|
|
基础部分
数据结构
Series 数据结构
Series 数据结构就是列表和字典的结合体,下面看一下具体演示
|
|
0 4
1 7
2 -5
3 3
dtype: int64
|
|
RangeIndex(start=0, stop=4, step=1)
|
|
array([ 4, 7, -5, 3], dtype=int64)
|
|
d 4
b 7
a -5
c 3
dtype: int64
|
|
Index(['d', 'b', 'a', 'c'], dtype='object')
|
|
array([ 4, 7, -5, 3], dtype=int64)
|
|
Ohio 35000
Texas 71000
Oregon 16000
Utah 5000
dtype: int64
可以传入排好序的字典的键以改变顺序
sdata 中跟 states 索引相匹配的那3个值会被找出来并放到相应的位置上,但由于”California”所对应的 sdata 值找不到,所以其结果就为 NaN(即“非数字”(not a number),在 pandas 中,它用于表示缺失或 NA 值)。因为‘Utah’不在 states 中,它被从结果中除去。
|
|
California NaN
Ohio 35000.0
Oregon 16000.0
Texas 71000.0
dtype: float64
|
|
False
|
|
True
Series 对象本身及其索引都有一个 name 属性,该属性跟pandas其他的关键功能关系非常密切:
|
|
state
California NaN
Ohio 35000.0
Oregon 16000.0
Texas 71000.0
Name: population, dtype: float64
Series 的索引可以通过赋值的方式就地修改
|
|
Bob NaN
Steve 35000.0
Jeff 16000.0
Ryan 71000.0
Name: population, dtype: float64
DataFrame 数据结构
DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典(共用同一个索引)。DataFrame中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。
columns 相当于 sql 中的字段, index 相当于 sql 中的索引
建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典
|
|
state | year | pop | |
---|---|---|---|
0 | Ohio | 2000 | 1.5 |
1 | Ohio | 2001 | 1.7 |
2 | Ohio | 2002 | 3.6 |
3 | Nevada | 2001 | 2.4 |
4 | Nevada | 2002 | 2.9 |
5 | Nevada | 2003 | 3.2 |
|
|
state | year | pop | |
---|---|---|---|
0 | Ohio | 2000 | 1.5 |
1 | Ohio | 2001 | 1.7 |
2 | Ohio | 2002 | 3.6 |
3 | Nevada | 2001 | 2.4 |
4 | Nevada | 2002 | 2.9 |
如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列
|
|
year | state | pop | |
---|---|---|---|
0 | 2000 | Ohio | 1.5 |
1 | 2001 | Ohio | 1.7 |
2 | 2002 | Ohio | 3.6 |
3 | 2001 | Nevada | 2.4 |
4 | 2002 | Nevada | 2.9 |
5 | 2003 | Nevada | 3.2 |
|
|
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN |
two | 2001 | Ohio | 1.7 | NaN |
three | 2002 | Ohio | 3.6 | NaN |
four | 2001 | Nevada | 2.4 | NaN |
five | 2002 | Nevada | 2.9 | NaN |
six | 2003 | Nevada | 3.2 | NaN |
|
|
Index(['year', 'state', 'pop', 'debt'], dtype='object')
|
|
one Ohio
two Ohio
three Ohio
four Nevada
five Nevada
six Nevada
Name: state, dtype: object
|
|
one Ohio
two Ohio
three Ohio
four Nevada
five Nevada
six Nevada
Name: state, dtype: object
|
|
year 2000
state Ohio
pop 1.5
debt NaN
Name: one, dtype: object
|
|
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | 16.5 |
two | 2001 | Ohio | 1.7 | 16.5 |
three | 2002 | Ohio | 3.6 | 16.5 |
four | 2001 | Nevada | 2.4 | 16.5 |
five | 2002 | Nevada | 2.9 | 16.5 |
six | 2003 | Nevada | 3.2 | 16.5 |
将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。如果赋值的是一个Series,就会精确匹配DataFrame的索引,所有的空位都将被填上缺失值:
|
|
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN |
two | 2001 | Ohio | 1.7 | -1.2 |
three | 2002 | Ohio | 3.6 | NaN |
four | 2001 | Nevada | 2.4 | -1.5 |
five | 2002 | Nevada | 2.9 | -1.7 |
six | 2003 | Nevada | 3.2 | NaN |
为不存在的列赋值会创建出一个新列。关键字del用于删除列。
|
|
year | state | pop | debt | test | |
---|---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN | True |
two | 2001 | Ohio | 1.7 | -1.2 | True |
three | 2002 | Ohio | 3.6 | NaN | True |
four | 2001 | Nevada | 2.4 | -1.5 | False |
five | 2002 | Nevada | 2.9 | -1.7 | False |
six | 2003 | Nevada | 3.2 | NaN | False |
|
|
year | state | pop | debt | |
---|---|---|---|---|
one | 2000 | Ohio | 1.5 | NaN |
two | 2001 | Ohio | 1.7 | -1.2 |
three | 2002 | Ohio | 3.6 | NaN |
four | 2001 | Nevada | 2.4 | -1.5 |
five | 2002 | Nevada | 2.9 | -1.7 |
six | 2003 | Nevada | 3.2 | NaN |
另一种常见的数据形式是嵌套字典:
如果嵌套字典传给DataFrame,pandas就会被解释为:外层字典的键作为列,内层键则作为行索引:
|
|
Nevada | Ohio | |
---|---|---|
2000 | NaN | 1.5 |
2001 | 2.4 | 1.7 |
2002 | 2.9 | 3.6 |
如果设置了 DataFrame 的 index 和 columns 的 name 属性,则这些信息也会被显示出来:
|
|
state | Nevada | Ohio |
---|---|---|
year | ||
2000 | NaN | 1.5 |
2001 | 2.4 | 1.7 |
2002 | 2.9 | 3.6 |
|
|
array([[nan, 1.5],
[2.4, 1.7],
[2.9, 3.6]])
索引对象
pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index:
DataFrame 的 index 和 columns 都是索引对象
|
|
Index(['a', 'b', 'c'], dtype='object')
|
|
state | Nevada | Ohio |
---|---|---|
year | ||
2000 | NaN | 1.5 |
2001 | 2.4 | 1.7 |
2002 | 2.9 | 3.6 |
|
|
Index(['Nevada', 'Ohio'], dtype='object', name='state')
|
|
Int64Index([2000, 2001, 2002], dtype='int64', name='year')
与python的集合不同,pandas的Index可以包含重复的标签:
|
|
Index(['foo', 'foo', 'bar', 'bar'], dtype='object')
Index 索引对象有一些方法和属性,不怎么用
|
|
Index(['foo', 'bar'], dtype='object')
基本功能
重新索引 reindex
pandas对象的一个重要方法是reindex,其作用是创建一个新对象,它的数据符合新的索引。
|
|
d 4.5
b 7.2
a -5.3
c 3.6
dtype: float64
用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值:
|
|
a -5.3
b 7.2
c 3.6
d 4.5
e NaN
dtype: float64
重新索引时可能需要做一些插值处理。method选项即可达到此目的,例如,使用 ffill 可以实现前向值填充
|
|
0 blue
2 purple
4 yellow
dtype: object
|
|
0 blue
1 blue
2 purple
3 purple
4 yellow
5 yellow
dtype: object
借助 DataFrame,reindex 可以修改(行)索引和列。只传递一个序列时,会重新索引结果的行:
|
|
Ohio | Texas | California | |
---|---|---|---|
a | 0 | 1 | 2 |
c | 3 | 4 | 5 |
d | 6 | 7 | 8 |
|
|
Ohio | Texas | California | |
---|---|---|---|
a | 0.0 | 1.0 | 2.0 |
b | NaN | NaN | NaN |
c | 3.0 | 4.0 | 5.0 |
d | 6.0 | 7.0 | 8.0 |
|
|
Texas | Utah | California | |
---|---|---|---|
a | 1 | NaN | 2 |
c | 4 | NaN | 5 |
d | 7 | NaN | 8 |
reindex函数的各参数及说明
丢弃指定轴上的项 drop
丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑,所以drop方法返回的是一个在指定轴上删除了指定值的新对象:
|
|
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
|
|
a 0.0
b 1.0
d 3.0
e 4.0
dtype: float64
对于DataFrame,可以删除任意轴上的索引值。 drop 默认会从行标签(axis 0)删除值(删除行), 通过传递 axis=1
或 axis='columns'
可以删除列的值
|
|
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
Utah | 8 | 9 | 10 | 11 |
New York | 12 | 13 | 14 | 15 |
|
|
one | two | three | four | |
---|---|---|---|---|
Colorado | 4 | 5 | 6 | 7 |
New York | 12 | 13 | 14 | 15 |
|
|
one | three | four | |
---|---|---|---|
Ohio | 0 | 2 | 3 |
Colorado | 4 | 6 | 7 |
Utah | 8 | 10 | 11 |
New York | 12 | 14 | 15 |
许多函数,如drop,会修改Series或DataFrame的大小或形状,可以就地修改对象,不会返回新的对象:
|
|
|
|
a 0.0
b 1.0
d 3.0
e 4.0
dtype: float64
索引、选取和过滤
Series索引的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是整数。
|
|
a 0.0
b 1.0
c 2.0
d 3.0
dtype: float64
|
|
0.0
|
|
0.0
|
|
a 0.0
b 1.0
dtype: float64
|
|
a 0.0
b 1.0
c 2.0
dtype: float64
|
|
a 0.0
b 1.0
d 3.0
dtype: float64
|
|
b 1.0
d 3.0
dtype: float64
|
|
a 0.0
b 1.0
dtype: float64
用一个值或序列对DataFrame进行索引其实就是获取一个或多个列:
用切片 [:]
切的是行
|
|
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
Utah | 8 | 9 | 10 | 11 |
New York | 12 | 13 | 14 | 15 |
|
|
Ohio 1
Colorado 5
Utah 9
New York 13
Name: two, dtype: int32
|
|
three | one | |
---|---|---|
Ohio | 2 | 0 |
Colorado | 6 | 4 |
Utah | 10 | 8 |
New York | 14 | 12 |
|
|
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
Utah | 8 | 9 | 10 | 11 |
|
|
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
用 loc 和 iloc 进行选取
特殊的标签运算符loc和iloc。它们可以让你用类似NumPy的标记,使用轴标签(loc)或整数索引(iloc),从DataFrame选择行和列的子集。
|
|
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
Utah | 8 | 9 | 10 | 11 |
New York | 12 | 13 | 14 | 15 |
|
|
one 0
two 1
Name: Ohio, dtype: int32
|
|
one | two | |
---|---|---|
Ohio | 0 | 1 |
Colorado | 4 | 5 |
Utah | 8 | 9 |
|
|
one 8
two 9
Name: Utah, dtype: int32
|
|
one | two | |
---|---|---|
Utah | 8 | 9 |
New York | 12 | 13 |
pandas可以勉强进行整数索引,但是会导致小bug。我们有包含0,1,2的索引,但这会引起歧义,为了进行统一,如果轴索引含有整数,数据选取总会使用标签。为了更准确,请使用loc(标签)或iloc(整数):
|
|
0 0.0
1 1.0
2 2.0
dtype: float64
|
|
对于非整数索引,不会产生歧义
|
|
2.0
算术运算和数据对齐、在算术方法中填充值
pandas最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。对于有数据库经验的用户,这就像在索引标签上进行自动外连接。
|
|
a 7.3
c -2.5
d 3.4
e 1.5
dtype: float64
|
|
a -2.1
c 3.6
e -1.5
f 4.0
g 3.1
dtype: float64
|
|
a 5.2
c 1.1
d NaN
e 0.0
f NaN
g NaN
dtype: float64
对于DataFrame,对齐操作会同时发生在行和列上
|
|
b | c | d | |
---|---|---|---|
Ohio | 0.0 | 1.0 | 2.0 |
Texas | 3.0 | 4.0 | 5.0 |
Colorado | 6.0 | 7.0 | 8.0 |
|
|
b | d | e | |
---|---|---|---|
Utah | 0.0 | 1.0 | 2.0 |
Ohio | 3.0 | 4.0 | 5.0 |
Texas | 6.0 | 7.0 | 8.0 |
Oregon | 9.0 | 10.0 | 11.0 |
|
|
b | c | d | e | |
---|---|---|---|---|
Colorado | NaN | NaN | NaN | NaN |
Ohio | 3.0 | NaN | 6.0 | NaN |
Oregon | NaN | NaN | NaN | NaN |
Texas | 9.0 | NaN | 12.0 | NaN |
Utah | NaN | NaN | NaN | NaN |
在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如0)
|
|
b | c | d | e | |
---|---|---|---|---|
Colorado | 6.0 | 7.0 | 8.0 | NaN |
Ohio | 3.0 | 1.0 | 6.0 | 5.0 |
Oregon | 9.0 | NaN | 10.0 | 11.0 |
Texas | 9.0 | 4.0 | 12.0 | 8.0 |
Utah | 0.0 | NaN | 1.0 | 2.0 |
其他算数方法:
以字母r开头,它会翻转参数。
|
|
0 0.5
1 0.5
2 0.5
dtype: float64
|
|
0 2.0
1 2.0
2 2.0
dtype: float64
DataFrame和Series之间的运算
DataFrame和Series之间的运算,默认是横向进行广播的,如果需要在列上进行广播,就必须使用算数方法计算指定 axis='index'
|
|
b | d | e | |
---|---|---|---|
Utah | 0.0 | 1.0 | 2.0 |
Ohio | 3.0 | 4.0 | 5.0 |
Texas | 6.0 | 7.0 | 8.0 |
Oregon | 9.0 | 10.0 | 11.0 |
|
|
b 0.0
d 1.0
e 2.0
Name: Utah, dtype: float64
|
|
b | d | e | |
---|---|---|---|
Utah | 0.0 | 2.0 | 4.0 |
Ohio | 3.0 | 5.0 | 7.0 |
Texas | 6.0 | 8.0 | 10.0 |
Oregon | 9.0 | 11.0 | 13.0 |
|
|
b | d | e | |
---|---|---|---|
Ohio | NaN | NaN | NaN |
Oregon | NaN | NaN | NaN |
Texas | NaN | NaN | NaN |
Utah | NaN | NaN | NaN |
b | NaN | NaN | NaN |
d | NaN | NaN | NaN |
e | NaN | NaN | NaN |
|
|
Utah 0.0
Ohio 3.0
Texas 6.0
Oregon 9.0
Name: b, dtype: float64
|
|
b | d | e | |
---|---|---|---|
Utah | 0.0 | 1.0 | 2.0 |
Ohio | 6.0 | 7.0 | 8.0 |
Texas | 12.0 | 13.0 | 14.0 |
Oregon | 18.0 | 19.0 | 20.0 |
函数应用和映射
将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能:
|
|
b | d | e | |
---|---|---|---|
Utah | -0.618453 | 0.743194 | -1.467120 |
Ohio | 0.272335 | 0.542511 | -0.458843 |
Texas | -0.762976 | 0.330646 | 0.988022 |
Oregon | -0.207557 | 0.425898 | 0.783251 |
|
|
b 1.035311
d 0.412548
e 2.455142
dtype: float64
如果传递 axis='columns'
到 apply ,这个函数会在每行执行:
|
|
Utah 2.210314
Ohio 1.001354
Texas 1.750998
Oregon 0.990809
dtype: float64
传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的Series:
|
|
b | d | e | |
---|---|---|---|
min | -0.762976 | 0.330646 | -1.467120 |
max | 0.272335 | 0.743194 | 0.988022 |
元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串,使用applymap即可:
|
|
b | d | e | |
---|---|---|---|
Utah | -0.62 | 0.74 | -1.47 |
Ohio | 0.27 | 0.54 | -0.46 |
Texas | -0.76 | 0.33 | 0.99 |
Oregon | -0.21 | 0.43 | 0.78 |
Series 有一个用于应用元素级函数的 map 方法
|
|
0 1.00
1 2.00
2 3.00
dtype: object
排序和排名
根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用 sort_index 方法,它将返回一个已排序的新对象:
|
|
d 0
a 1
b 2
c 3
dtype: int64
按照索引排序 sort_index
|
|
a 1
b 2
c 3
d 0
dtype: int64
|
|
d | a | b | c | |
---|---|---|---|---|
three | 0 | 1 | 2 | 3 |
one | 4 | 5 | 6 | 7 |
|
|
d | a | b | c | |
---|---|---|---|---|
one | 4 | 5 | 6 | 7 |
three | 0 | 1 | 2 | 3 |
|
|
a | b | c | d | |
---|---|---|---|---|
three | 1 | 2 | 3 | 0 |
one | 5 | 6 | 7 | 4 |
数据默认是按升序排序的,但也可以降序排序:
|
|
d | c | b | a | |
---|---|---|---|---|
three | 0 | 3 | 2 | 1 |
one | 4 | 7 | 6 | 5 |
按照值排序 sort_values
|
|
d 0
a 1
b 2
c 3
dtype: int64
|
|
2 -3
3 2
0 4
1 7
dtype: int64
当排序一个DataFrame时,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给 sort_values 的 by 选项即可达到该目的:
|
|
b | a | |
---|---|---|
0 | 4 | 0 |
1 | 7 | 1 |
2 | -3 | 0 |
3 | 2 | 1 |
|
|
b | a | |
---|---|---|
2 | -3 | 0 |
3 | 2 | 1 |
0 | 4 | 0 |
1 | 7 | 1 |
|
|
b | a | |
---|---|---|
2 | -3 | 0 |
0 | 4 | 0 |
3 | 2 | 1 |
1 | 7 | 1 |
默认情况下,rank是通过“为各组分配一个平均排名”的方式破坏平级关系的:
|
|
0 7
1 -5
2 7
3 4
4 2
5 0
6 4
dtype: int64
|
|
0 6.5
1 1.0
2 6.5
3 4.5
4 3.0
5 2.0
6 4.5
dtype: float64
也可以根据值在原数据中出现的顺序给出排名:
|
|
0 6.0
1 1.0
2 7.0
3 4.0
4 3.0
5 2.0
6 5.0
dtype: float64
你也可以按降序进行排名:
|
|
0 1.0
1 7.0
2 2.0
3 3.0
4 5.0
5 6.0
6 4.0
dtype: float64
rank 方法在 DataFrame 可以在行或列上计算排名:
|
|
b | a | c | |
---|---|---|---|
0 | 4.3 | 0 | -2.0 |
1 | 7.0 | 1 | 5.0 |
2 | -3.0 | 0 | 8.0 |
3 | 2.0 | 1 | -2.5 |
|
|
b | a | c | |
---|---|---|---|
0 | 3.0 | 2.0 | 1.0 |
1 | 3.0 | 1.0 | 2.0 |
2 | 1.0 | 2.0 | 3.0 |
3 | 3.0 | 2.0 | 1.0 |
排名时用于破坏平级关系的方法
带有重复标签的轴索引
重复的标签会导致获取数据是数据结构复杂,不建议使用
|
|
a 0
a 1
b 2
b 3
c 4
dtype: int64
|
|
False
|
|
a 0
a 1
dtype: int64
汇总和计算描述统计
pandas对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从Series中提取单个值(如sum或mean)或从DataFrame的行或列中提取一个Series。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的。
|
|
one | two | |
---|---|---|
a | 1.40 | NaN |
b | 7.10 | -4.5 |
c | NaN | NaN |
d | 0.75 | -1.3 |
|
|
one 9.25
two -5.80
dtype: float64
|
|
a 1.40
b 2.60
c 0.00
d -0.55
dtype: float64
NA值会自动被排除,除非整个切片(这里指的是行或列)都是NA。通过skipna选项可以禁用该功能:
|
|
a NaN
b 2.60
c NaN
d -0.55
dtype: float64
所有与描述统计相关的方法
相关系数与协方差
有些汇总统计(如相关系数和协方差)是通过参数对计算出来的。我们来看几个DataFrame,它们的数据来自 Yahoo!Finance 的股票价格和成交量,使用的是 pandas-datareader 包(可以用conda或pip安装):
使用pandas_datareader模块下载了一些股票数据:
|
|
|
|
AAPL | IBM | MSFT | GOOG | |
---|---|---|---|---|
Date | ||||
2019-04-11 | -0.008324 | 0.005314 | 0.001165 | 0.002046 |
2019-04-12 | -0.000402 | 0.003964 | 0.005152 | 0.010999 |
2019-04-15 | 0.001810 | -0.003118 | 0.000827 | 0.002652 |
2019-04-16 | 0.000100 | 0.008617 | -0.002313 | 0.004938 |
2019-04-17 | 0.019473 | -0.041546 | 0.008280 | 0.007505 |
Series 的 corr 方法用于计算两个 Series 中重叠的、非NA的、按索引对齐的值的相关系数。与此类似, cov 用于计算协方差:
|
|
0.4865732693083368
|
|
8.677797505286974e-05
DataFrame 的 corr 和 cov 方法将以 DataFrame 的形式分别返回完整的相关系数或协方差矩阵:
|
|
AAPL | IBM | MSFT | GOOG | |
---|---|---|---|---|
AAPL | 1.000000 | 0.370756 | 0.452098 | 0.458110 |
IBM | 0.370756 | 1.000000 | 0.486573 | 0.407424 |
MSFT | 0.452098 | 0.486573 | 1.000000 | 0.537616 |
GOOG | 0.458110 | 0.407424 | 0.537616 | 1.000000 |
|
|
AAPL | IBM | MSFT | GOOG | |
---|---|---|---|---|
AAPL | 0.000269 | 0.000075 | 0.000107 | 0.000116 |
IBM | 0.000075 | 0.000152 | 0.000087 | 0.000077 |
MSFT | 0.000107 | 0.000087 | 0.000209 | 0.000120 |
GOOG | 0.000116 | 0.000077 | 0.000120 | 0.000236 |
利用DataFrame的corrwith方法,你可以计算其列或行跟另一个Series或DataFrame之间的相关系数。传入一个Series将会返回一个相关系数值Series(针对各列进行计算):
|
|
AAPL 0.370756
IBM 1.000000
MSFT 0.486573
GOOG 0.407424
dtype: float64
传入一个DataFrame则会计算按列名配对的相关系数。这里,我计算百分比变化与成交量的相关系数:
|
|
AAPL | IBM | MSFT | GOOG | |
---|---|---|---|---|
Date | ||||
2009-12-31 | 88102700.0 | 4223400.0 | 31929700.0 | 2455400.0 |
2010-01-04 | 123432400.0 | 6155300.0 | 38409100.0 | 3937800.0 |
2010-01-05 | 150476200.0 | 6841400.0 | 49749600.0 | 6048500.0 |
2010-01-06 | 138040000.0 | 5605300.0 | 58182400.0 | 8009000.0 |
2010-01-07 | 119282800.0 | 5840600.0 | 50559700.0 | 12912000.0 |
|
|
AAPL -0.061663
IBM -0.156416
MSFT -0.089665
GOOG -0.018289
dtype: float64
|
|
AAPL 1.0
IBM 1.0
MSFT 1.0
GOOG 1.0
dtype: float64
传入
axis='columns'
即可按行进行计算。无论如何,在计算相关系数之前,所有的数据项都会按标签对齐。
唯一值、值计数以及成员资格
unique 可以得到Series中的唯一值数组:
|
|
array(['c', 'a', 'd', 'b'], dtype=object)
value_counts 用于计算一个 Series 中各值出现的频率:
|
|
c 3
a 3
b 2
d 1
dtype: int64
为了便于查看,结果Series是按值频率降序排列的。value_counts还是一个顶级pandas方法,可用于任何数组或序列:
|
|
b 2
d 1
a 3
c 3
dtype: int64
isin用于判断矢量化集合的成员资格,可用于过滤Series中或DataFrame列中数据的子集:
|
|
0 c
1 a
2 d
3 a
4 a
5 b
6 b
7 c
8 c
dtype: object
|
|
0 True
1 False
2 False
3 False
4 False
5 True
6 True
7 True
8 True
dtype: bool
|
|
0 c
5 b
6 b
7 c
8 c
dtype: object
数据加载、存储与文件格式
读写文本格式的数据
pandas提供了一些用于将表格型数据读取为DataFrame对象的函数。表6-1对它们进行了总结,其中read_csv和read_table可能会是你今后用得最多的。
其中一些函数,比如pandas.read_csv,有类型推断功能,因为列数据的类型不属于数据类型。也就是说,你不需要指定列的类型到底是数值、整数、布尔值,还是字符串。其它的数据格式,如HDF5、Feather和msgpack,会在格式中存储数据类型。
读取 csv 文件
|
|
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
由于该文件以逗号分隔,所以我们可以使用read_csv将其读入一个DataFrame:
|
|
a | b | c | d | message | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | hello |
1 | 5 | 6 | 7 | 8 | world |
2 | 9 | 10 | 11 | 12 | foo |
我们还可以使用read_table,并指定分隔符:
|
|
a | b | c | d | message | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | hello |
1 | 5 | 6 | 7 | 8 | world |
2 | 9 | 10 | 11 | 12 | foo |
并不是所有文件都有标题行。看看下面这个文件:
|
|
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
读入没有标题行的办法有两个。你可以让pandas为其分配默认的列名,也可以自己定义列名:
|
|
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | hello |
1 | 5 | 6 | 7 | 8 | world |
2 | 9 | 10 | 11 | 12 | foo |
|
|
a | b | c | d | message | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | hello |
1 | 5 | 6 | 7 | 8 | world |
2 | 9 | 10 | 11 | 12 | foo |
指定某一列作为列索引
|
|
a | b | c | d | |
---|---|---|---|---|
message | ||||
hello | 1 | 2 | 3 | 4 |
world | 5 | 6 | 7 | 8 |
foo | 9 | 10 | 11 | 12 |
如果希望将多个列做成一个层次化索引,只需传入由列编号或列名组成的列表即可:
|
|
key1,key2,value1,value2
one,a,1,2
one,b,3,4
one,c,5,6
one,d,7,8
two,a,9,10
two,b,11,12
two,c,13,14
two,d,15,16
|
|
value1 | value2 | ||
---|---|---|---|
key1 | key2 | ||
one | a | 1 | 2 |
b | 3 | 4 | |
c | 5 | 6 | |
d | 7 | 8 | |
two | a | 9 | 10 |
b | 11 | 12 | |
c | 13 | 14 | |
d | 15 | 16 |
有些表格可能不是用固定的分隔符去分隔字段的(比如空白符或其它模式)。看看下面这个文本文件:
|
|
[' A B C\n',
'aaa -0.264438 -1.026059 -0.619500\n',
'bbb 0.927272 0.302904 -0.032399\n',
'ccc -0.264273 -0.386314 -0.217601\n',
'ddd -0.871858 -0.348382 1.100491\n']
虽然可以手动对数据进行规整,这里的字段是被数量不同的空白字符间隔开的。这种情况下,你可以传递一个正则表达式作为read_table的分隔符。可以用正则表达式表达为 \s+
,于是有:
|
|
A | B | C | |
---|---|---|---|
aaa | -0.264438 | -1.026059 | -0.619500 |
bbb | 0.927272 | 0.302904 | -0.032399 |
ccc | -0.264273 | -0.386314 | -0.217601 |
ddd | -0.871858 | -0.348382 | 1.100491 |
可以用skiprows跳过文件的第一行、第三行和第四行:
|
|
# hey!
a,b,c,d,message
# just wanted to make things more difficult for you
# who reads CSV files with computers, anyway?
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
|
|
a | b | c | d | message | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | hello |
1 | 5 | 6 | 7 | 8 | world |
2 | 9 | 10 | 11 | 12 | foo |
缺失值处理是文件解析任务中的一个重要组成部分。缺失数据经常是要么没有(空字符串),要么用某个标记值表示。默认情况下,pandas会用一组经常出现的标记值进行识别,比如NA及NULL:
|
|
something,a,b,c,d,message
one,1,2,3,4,NA
two,5,6,,8,world
three,9,10,11,12,foo
|
|
something | a | b | c | d | message | |
---|---|---|---|---|---|---|
0 | one | 1 | 2 | 3.0 | 4 | NaN |
1 | two | 5 | 6 | NaN | 8 | world |
2 | three | 9 | 10 | 11.0 | 12 | foo |
na_values 可以用一个列表或集合的字符串表示缺失值:(把指定的值填充成 Nan)
|
|
something | a | b | c | d | message | |
---|---|---|---|---|---|---|
0 | one | 1 | 2 | 3.0 | 4 | NaN |
1 | two | 5 | 6 | NaN | 8 | world |
2 | three | 9 | 10 | 11.0 | 12 | foo |
字典的各列可以对不同的值用NA进行标记:
|
|
something | a | b | c | d | message | |
---|---|---|---|---|---|---|
0 | one | 1 | 2 | 3.0 | 4 | NaN |
1 | NaN | 5 | 6 | NaN | 8 | world |
2 | three | 9 | 10 | 11.0 | 12 | NaN |
pandas.read_csv和pandas.read_table常用的选项。
逐块读取文本文件
在处理很大的文件时,或找出大文件中的参数集以便于后续处理时,你可能只想读取文件的一小部分或逐块对文件进行迭代。
在看大文件之前,我们先设置pandas显示地更紧些:
|
|
|
|
one | two | three | four | key | |
---|---|---|---|---|---|
0 | 0.467976 | -0.038649 | -0.295344 | -1.824726 | L |
1 | -0.358893 | 1.404453 | 0.704965 | -0.200638 | B |
2 | -0.501840 | 0.659254 | -0.421691 | -0.057688 | G |
3 | 0.204886 | 1.074134 | 1.388361 | -0.982404 | R |
4 | 0.354628 | -0.133116 | 0.283763 | -0.837063 | Q |
... | ... | ... | ... | ... | ... |
9995 | 2.311896 | -0.417070 | -1.409599 | -0.515821 | L |
9996 | -0.479893 | -0.650419 | 0.745152 | -0.646038 | E |
9997 | 0.523331 | 0.787112 | 0.486066 | 1.093156 | K |
9998 | -0.362559 | 0.598894 | -1.843201 | 0.887292 | G |
9999 | -0.096376 | -1.012999 | -0.657431 | -0.573315 | 0 |
10000 rows × 5 columns
如果只想读取几行(避免读取整个文件),通过nrows进行指定即可:
|
|
one | two | three | four | key | |
---|---|---|---|---|---|
0 | 0.467976 | -0.038649 | -0.295344 | -1.824726 | L |
1 | -0.358893 | 1.404453 | 0.704965 | -0.200638 | B |
2 | -0.501840 | 0.659254 | -0.421691 | -0.057688 | G |
3 | 0.204886 | 1.074134 | 1.388361 | -0.982404 | R |
4 | 0.354628 | -0.133116 | 0.283763 | -0.837063 | Q |
要逐块读取文件,可以指定chunksize(行数):
|
|
<pandas.io.parsers.TextFileReader at 0xa1a9710>
|
|
one two three four key
9998 -0.362559 0.598894 -1.843201 0.887292 G
9999 -0.096376 -1.012999 -0.657431 -0.573315 0
将数据写出到文本格式
|
|
something | a | b | c | d | message | |
---|---|---|---|---|---|---|
0 | one | 1 | 2 | 3.0 | 4 | NaN |
1 | two | 5 | 6 | NaN | 8 | world |
2 | three | 9 | 10 | 11.0 | 12 | foo |
|
|
|
|
,something,a,b,c,d,message
0,one,1,2,3.0,4,
1,two,5,6,,8,world
2,three,9,10,11.0,12,foo
当然,还可以使用其他分隔符(由于这里直接写出到sys.stdout,所以仅仅是打印出文本结果而已):
|
|
|
|
|something|a|b|c|d|message
0|one|1|2|3.0|4|
1|two|5|6||8|world
2|three|9|10|11.0|12|foo
禁止输出行和列的标签(索引)
|
|
one,1,2,3.0,4,
two,5,6,,8,world
three,9,10,11.0,12,foo
输出部分列
|
|
a,b,c
1,2,3.0
5,6,
9,10,11.0
JSON数据
|
|
{'name': 'Wes',
'places_lived': ['United States', 'Spain', 'Germany'],
'pet': None,
'siblings': [{'name': 'Scott', 'age': 30, 'pets': ['Zeus', 'Zuko']},
{'name': 'Katie', 'age': 38, 'pets': ['Sixes', 'Stache', 'Cisco']}]}
|
|
name | age | |
---|---|---|
0 | Scott | 30 |
1 | Katie | 38 |
pandas.read_json可以自动将特别格式的JSON数据集转换为Series或DataFrame。例如:
|
|
[{"a": 1, "b": 2, "c": 3},
{"a": 4, "b": 5, "c": 6},
{"a": 7, "b": 8, "c": 9}]
|
|
a | b | c | |
---|---|---|---|
0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 |
2 | 7 | 8 | 9 |
将数据从pandas输出到JSON,可以使用to_json方法:
|
|
'{"a":{"0":1,"1":4,"2":7},"b":{"0":2,"1":5,"2":8},"c":{"0":3,"1":6,"2":9}}'
|
|
'[{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}]'
二进制数据格式
实现数据的高效二进制格式存储最简单的办法之一是使用Python内置的pickle序列化。pandas对象都有一个用于将数据以pickle格式保存到磁盘上的to_pickle方法:
|
|
a | b | c | d | message | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | hello |
1 | 5 | 6 | 7 | 8 | world |
2 | 9 | 10 | 11 | 12 | foo |
|
|
通过pickle直接读取被pickle化的数据,或是使用更为方便的pandas.read_pickle:
|
|
a | b | c | d | message | |
---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | hello |
1 | 5 | 6 | 7 | 8 | world |
2 | 9 | 10 | 11 | 12 | foo |
注意:pickle仅建议用于短期存储格式。其原因是很难保证该格式永远是稳定的;今天pickle的对象可能无法被后续版本的库unpickle出来。虽然我尽力保证这种事情不会发生在pandas中,但是今后的某个时候说不定还是得“打破”该pickle格式。
使用HDF5格式
HDF5是一种存储大规模科学数组数据的非常好的文件格式。它可以被作为C标准库,带有许多语言的接口,如Java、Python和MATLAB等。HDF5中的HDF指的是层次型数据格式(hierarchical data format)。每个HDF5文件都含有一个文件系统式的节点结构,它使你能够存储多个数据集并支持元数据。与其他简单格式相比,HDF5支持多种压缩器的即时压缩,还能更高效地存储重复模式数据。对于那些非常大的无法直接放入内存的数据集,HDF5就是不错的选择,因为它可以高效地分块读写。
虽然可以用PyTables或h5py库直接访问HDF5文件,pandas提供了更为高级的接口,可以简化存储Series和DataFrame对象。HDFStore类可以像字典一样,处理低级的细节:
|
|
a | |
---|---|
0 | 1.863684 |
1 | 0.743116 |
2 | -0.656781 |
3 | 0.349087 |
4 | -0.772184 |
|
|
|
|
|
|
|
|
<class 'pandas.io.pytables.HDFStore'>
File path: data/examples/mydata.h5
|
|
|
|
|
|
a | |
---|---|
0 | 1.863684 |
1 | 0.743116 |
2 | -0.656781 |
3 | 0.349087 |
4 | -0.772184 |
读取Microsoft Excel文件
pandas的ExcelFile类或pandas.read_excel函数支持读取存储在Excel 2003(或更高版本)中的表格型数据。这两个工具分别使用扩展包xlrd和openpyxl读取XLS和XLSX文件。你可以用pip或conda安装它们。
|
|
Unnamed: 0 | a | b | c | d | message | |
---|---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 | hello |
1 | 1 | 5 | 6 | 7 | 8 | world |
2 | 2 | 9 | 10 | 11 | 12 | foo |
如果要读取一个文件中的多个表单,创建ExcelFile会更快,但你也可以将文件名传递到pandas.read_excel:
|
|
Unnamed: 0 | a | b | c | d | message | |
---|---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 | hello |
1 | 1 | 5 | 6 | 7 | 8 | world |
2 | 2 | 9 | 10 | 11 | 12 | foo |
如果要将pandas数据写入为Excel格式,你必须首先创建一个ExcelWriter,然后使用pandas对象的to_excel方法将数据写入到其中:
|
|
你还可以不使用ExcelWriter,而是传递文件的路径到to_excel:
|
|
数据库交互
将数据从SQL加载到DataFrame的过程很简单,此外pandas还有一些能够简化该过程的函数。
例如,我将使用SQLite数据库(通过Python内置的sqlite3驱动器):
|
|
[('Atlanta', 'Georgia', 1.25, 6),
('Tallahassee', 'Florida', 2.6, 3),
('Sacramento', 'California', 1.7, 5)]
|
|
(('a', None, None, None, None, None, None),
('b', None, None, None, None, None, None),
('c', None, None, None, None, None, None),
('d', None, None, None, None, None, None))
|
|
a | b | c | d | |
---|---|---|---|---|
0 | Atlanta | Georgia | 1.25 | 6 |
1 | Tallahassee | Florida | 2.60 | 3 |
2 | Sacramento | California | 1.70 | 5 |
pandas 有一个 read_sql 函数,可以直接通过 sql 进行读取数据
|
|
|
|
a | b | c | d | |
---|---|---|---|---|
0 | Atlanta | Georgia | 1.25 | 6 |
1 | Tallahassee | Florida | 2.60 | 3 |
2 | Sacramento | California | 1.70 | 5 |
数据清洗和准备
处理缺失数据
缺失数据在pandas中呈现的方式有些不完美,但对于大多数用户可以保证功能正常。对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据。我们称其为哨兵值,可以方便的检测出来:
|
|
0 aardvark
1 artichoke
2 NaN
3 avocado
dtype: object
|
|
0 False
1 False
2 True
3 False
dtype: bool
Python内置的None值在对象数组中也可以作为NA
|
|
0 None
1 artichoke
2 NaN
3 avocado
dtype: object
|
|
0 True
1 False
2 True
3 False
dtype: bool
一些关于缺失数据处理的函数。
滤除缺失数据
对于一个Series,dropna返回一个仅含非空数据和索引值的Series:
|
|
0 1.0
1 NaN
2 3.5
3 NaN
4 7.0
dtype: float64
|
|
0 1.0
2 3.5
4 7.0
dtype: float64
|
|
0 1.0
2 3.5
4 7.0
dtype: float64
而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的行:
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.5 | 3.0 |
1 | 1.0 | NaN | NaN |
2 | NaN | NaN | NaN |
3 | NaN | 6.5 | 3.0 |
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.5 | 3.0 |
传入 how='all'
将只丢弃全为NA的那些行:
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.5 | 3.0 |
1 | 1.0 | NaN | NaN |
3 | NaN | 6.5 | 3.0 |
丢弃列,只需传入axis=1即可:
|
|
0 | 1 | 2 | 4 | |
---|---|---|---|---|
0 | 1.0 | 6.5 | 3.0 | NaN |
1 | 1.0 | NaN | NaN | NaN |
2 | NaN | NaN | NaN | NaN |
3 | NaN | 6.5 | 3.0 | NaN |
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 6.5 | 3.0 |
1 | 1.0 | NaN | NaN |
2 | NaN | NaN | NaN |
3 | NaN | 6.5 | 3.0 |
thresh
参数 删除 行、列 空值超过指定数字的
|
|
0 | 1 | 2 | 4 | |
---|---|---|---|---|
0 | 1.0 | 6.5 | 3.0 | NaN |
1 | 1.0 | NaN | NaN | NaN |
2 | NaN | NaN | NaN | NaN |
3 | NaN | 6.5 | 3.0 | NaN |
|
|
0 | 1 | 2 | 4 | |
---|---|---|---|---|
0 | 1.0 | 6.5 | 3.0 | NaN |
3 | NaN | 6.5 | 3.0 | NaN |
填充缺失数据
对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值:
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 0.187550 | NaN | NaN |
1 | -0.903578 | NaN | NaN |
2 | 0.142988 | NaN | -1.629844 |
3 | 0.346151 | NaN | -0.806237 |
4 | 0.125914 | -0.188009 | -1.067930 |
5 | -1.181948 | 0.289074 | -0.852676 |
6 | 0.568097 | -0.950069 | -2.165337 |
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 0.187550 | 0.000000 | 0.000000 |
1 | -0.903578 | 0.000000 | 0.000000 |
2 | 0.142988 | 0.000000 | -1.629844 |
3 | 0.346151 | 0.000000 | -0.806237 |
4 | 0.125914 | -0.188009 | -1.067930 |
5 | -1.181948 | 0.289074 | -0.852676 |
6 | 0.568097 | -0.950069 | -2.165337 |
若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值:
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 0.187550 | 0.500000 | 0.000000 |
1 | -0.903578 | 0.500000 | 0.000000 |
2 | 0.142988 | 0.500000 | -1.629844 |
3 | 0.346151 | 0.500000 | -0.806237 |
4 | 0.125914 | -0.188009 | -1.067930 |
5 | -1.181948 | 0.289074 | -0.852676 |
6 | 0.568097 | -0.950069 | -2.165337 |
fillna默认会返回新对象,但也可以对现有对象进行就地修改:
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 0.187550 | 0.000000 | 0.000000 |
1 | -0.903578 | 0.000000 | 0.000000 |
2 | 0.142988 | 0.000000 | -1.629844 |
3 | 0.346151 | 0.000000 | -0.806237 |
4 | 0.125914 | -0.188009 | -1.067930 |
5 | -1.181948 | 0.289074 | -0.852676 |
6 | 0.568097 | -0.950069 | -2.165337 |
对 reindex 有效的那些插值方法也可用于fillna:
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | -2.190504 | -1.963825 | -1.545763 |
1 | 0.306294 | -1.290580 | -1.274680 |
2 | 0.535840 | NaN | 0.219375 |
3 | 0.938590 | NaN | -1.277384 |
4 | 0.747602 | NaN | NaN |
5 | -0.113758 | NaN | NaN |
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | -2.190504 | -1.963825 | -1.545763 |
1 | 0.306294 | -1.290580 | -1.274680 |
2 | 0.535840 | -1.290580 | 0.219375 |
3 | 0.938590 | -1.290580 | -1.277384 |
4 | 0.747602 | -1.290580 | -1.277384 |
5 | -0.113758 | -1.290580 | -1.277384 |
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | -2.190504 | -1.963825 | -1.545763 |
1 | 0.306294 | -1.290580 | -1.274680 |
2 | 0.535840 | -1.290580 | 0.219375 |
3 | 0.938590 | -1.290580 | -1.277384 |
4 | 0.747602 | NaN | -1.277384 |
5 | -0.113758 | NaN | -1.277384 |
可以传入Series的平均值或中位数:
|
|
0 1.0
1 NaN
2 3.5
3 NaN
4 7.0
dtype: float64
|
|
0 1.000000
1 3.833333
2 3.500000
3 3.833333
4 7.000000
dtype: float64
fillna函数参数
数据转换
移除重复数据
|
|
k1 | k2 | |
---|---|---|
0 | one | 1 |
1 | two | 1 |
2 | one | 2 |
3 | two | 3 |
4 | one | 3 |
5 | two | 4 |
6 | two | 4 |
duplicated 方法返回一个布尔型 Series,表示各行是否是重复行(前面出现过的行):
|
|
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool
drop_duplicates 方法,它会返回一个DataFrame,重复的数据会被删除
|
|
k1 | k2 | |
---|---|---|
0 | one | 1 |
1 | two | 1 |
2 | one | 2 |
3 | two | 3 |
4 | one | 3 |
5 | two | 4 |
这两个方法默认会判断全部列,你也可以指定部分列进行重复项判断。
|
|
k1 | k2 | |
---|---|---|
0 | one | 1 |
1 | two | 1 |
duplicated 和 drop_duplicates 默认保留的是第一个出现的值组合。传入 keep='last'
则保留最后一个:
|
|
k1 | k2 | |
---|---|---|
0 | one | 1 |
1 | two | 1 |
2 | one | 2 |
3 | two | 3 |
4 | one | 3 |
6 | two | 4 |
利用函数或映射进行数据转换
利用函数或映射进行数据转换
我们来看看下面这组有关肉类的数据:
|
|
food | ounces | |
---|---|---|
0 | bacon | 4.0 |
1 | pulled pork | 3.0 |
2 | bacon | 12.0 |
3 | Pastrami | 6.0 |
4 | corned beef | 7.5 |
5 | Bacon | 8.0 |
6 | pastrami | 3.0 |
7 | honey ham | 5.0 |
8 | nova lox | 6.0 |
假设你想要添加一列表示该肉类食物来源的动物类型。我们先编写一个不同肉类到动物的映射:
|
|
列字符转换成小写
|
|
0 bacon
1 pulled pork
2 bacon
3 pastrami
4 corned beef
5 bacon
6 pastrami
7 honey ham
8 nova lox
Name: food, dtype: object
|
|
0 pig
1 pig
2 pig
3 cow
4 cow
5 pig
6 cow
7 pig
8 salmon
Name: food, dtype: object
|
|
food | ounces | animal | |
---|---|---|---|
0 | bacon | 4.0 | pig |
1 | pulled pork | 3.0 | pig |
2 | bacon | 12.0 | pig |
3 | Pastrami | 6.0 | cow |
4 | corned beef | 7.5 | cow |
5 | Bacon | 8.0 | pig |
6 | pastrami | 3.0 | cow |
7 | honey ham | 5.0 | pig |
8 | nova lox | 6.0 | salmon |
我们也可以使用下面的这种方式
|
|
0 pig
1 pig
2 pig
3 cow
4 cow
5 pig
6 cow
7 pig
8 salmon
Name: food, dtype: object
替换值
|
|
0 1.0
1 -999.0
2 2.0
3 -999.0
4 -1000.0
5 3.0
dtype: float64
-999这个值可能是一个表示缺失数据的标记值。要将其替换为pandas能够理解的NA值,我们可以利用replace来产生一个新的Series(除非传入inplace=True):
|
|
0 1.0
1 NaN
2 2.0
3 NaN
4 -1000.0
5 3.0
dtype: float64
如果你希望一次性替换多个值,可以传入一个由待替换值组成的列表以及一个替换值:
|
|
0 1.0
1 NaN
2 2.0
3 NaN
4 NaN
5 3.0
dtype: float64
要让每个值有不同的替换值,可以传递一个替换列表:
|
|
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
传入的参数也可以是字典:
|
|
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
重命名轴索引
|
|
one | two | three | four | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colorado | 4 | 5 | 6 | 7 |
New York | 8 | 9 | 10 | 11 |
跟Series一样,轴索引也有一个map方法:
|
|
Index(['OHIO', 'COLO', 'NEW '], dtype='object')
将其赋值给index,这样就可以对DataFrame进行就地修改:
|
|
one | two | three | four | |
---|---|---|---|---|
OHIO | 0 | 1 | 2 | 3 |
COLO | 4 | 5 | 6 | 7 |
NEW | 8 | 9 | 10 | 11 |
如果想要创建数据集的转换版(而不是修改原始数据),比较实用的方法是rename:
|
|
ONE | TWO | THREE | FOUR | |
---|---|---|---|---|
Ohio | 0 | 1 | 2 | 3 |
Colo | 4 | 5 | 6 | 7 |
New | 8 | 9 | 10 | 11 |
rename可以结合字典型对象实现对部分轴标签的更新:
|
|
one | two | peekaboo | four | |
---|---|---|---|---|
INDIANA | 0 | 1 | 2 | 3 |
COLO | 4 | 5 | 6 | 7 |
NEW | 8 | 9 | 10 | 11 |
rename可以实现复制DataFrame并对其索引和列标签进行赋值。如果希望就地修改某个数据集,传入 inplace=True
即可:
|
|
one | two | three | four | |
---|---|---|---|---|
INDIANA | 0 | 1 | 2 | 3 |
COLO | 4 | 5 | 6 | 7 |
NEW | 8 | 9 | 10 | 11 |
离散化和面元划分
为了便于分析,连续数据常常被离散化或拆分为“面元”(bin)。假设有一组人员数据,而你希望将它们划分为不同的年龄组:
|
|
接下来将这些数据划分为“18到25”、“26到35”、“35到60”以及“60以上”几个面元。要实现该功能,你需要使用pandas的cut函数:
|
|
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
pandas返回的是一个特殊的Categorical对象。结果展示了pandas.cut划分的面元。你可以将其看做一组表示面元名称的字符串。它的底层含有一个表示不同分类名称的类型数组,以及一个codes属性中的年龄数据的标签:
|
|
array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
|
|
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
|
|
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
跟“区间”的数学符号一样,圆括号表示开端,而方括号则表示闭端(包括)。哪边是闭端可以通过right=False进行修改:
|
|
[[18, 26), [18, 26), [18, 26), [26, 36), [18, 26), ..., [26, 36), [61, 100), [36, 61), [36, 61), [26, 36)]
Length: 12
Categories (4, interval[int64]): [[18, 26) < [26, 36) < [36, 61) < [61, 100)]
通过传递一个列表或数组到labels,设置自己的面元名称:
|
|
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]
如果向cut传入的是面元的数量而不是确切的面元边界,则它会根据数据的最小值和最大值计算等长面元。
|
|
[(0.49, 0.72], (0.015, 0.25], (0.49, 0.72], (0.49, 0.72], (0.25, 0.49], ..., (0.25, 0.49], (0.25, 0.49], (0.72, 0.96], (0.015, 0.25], (0.25, 0.49]]
Length: 20
Categories (4, interval[float64]): [(0.015, 0.25] < (0.25, 0.49] < (0.49, 0.72] < (0.72, 0.96]]
qcut是一个非常类似于cut的函数,它可以根据样本分位数对数据进行面元划分。根据数据的分布情况,cut可能无法使各个面元中含有相同数量的数据点。而qcut由于使用的是样本分位数,因此可以得到大小基本相等的面元:
|
|
[(-0.694, -0.0168], (-3.589, -0.694], (0.638, 3.369], (-0.0168, 0.638], (-3.589, -0.694], ..., (-0.694, -0.0168], (-3.589, -0.694], (-0.694, -0.0168], (-3.589, -0.694], (-0.0168, 0.638]]
Length: 1000
Categories (4, interval[float64]): [(-3.589, -0.694] < (-0.694, -0.0168] < (-0.0168, 0.638] < (0.638, 3.369]]
|
|
(0.638, 3.369] 250
(-0.0168, 0.638] 250
(-0.694, -0.0168] 250
(-3.589, -0.694] 250
dtype: int64
检测和过滤异常值
过滤或变换异常值在很大程度上就是运用数组运算。来看一个含有正态分布数据的DataFrame:
|
|
0 | 1 | 2 | 3 | |
---|---|---|---|---|
count | 1000.000000 | 1000.000000 | 1000.000000 | 1000.000000 |
mean | -0.064053 | -0.005447 | 0.038026 | 0.024811 |
std | 0.999489 | 0.948043 | 0.953395 | 1.021102 |
min | -3.105504 | -2.844008 | -2.760611 | -4.492430 |
25% | -0.718899 | -0.656214 | -0.597055 | -0.646600 |
50% | -0.020346 | -0.019434 | 0.058547 | -0.003166 |
75% | 0.578055 | 0.649719 | 0.661221 | 0.690373 |
max | 3.106261 | 2.717680 | 3.289203 | 3.243883 |
假设你想要找出某列中绝对值大小超过3的值
|
|
308 3.289203
Name: 2, dtype: float64
要选出全部含有“超过3或-3的值”的行,你可以在布尔型DataFrame中使用any方法:
|
|
0 | 1 | 2 | 3 | |
---|---|---|---|---|
8 | -3.105504 | -0.094313 | -0.186817 | 0.461499 |
96 | 0.522268 | -0.201284 | 0.145961 | 3.151065 |
121 | 3.106261 | -2.029135 | -0.179609 | -0.478008 |
148 | 0.019824 | 0.115120 | 1.005103 | -4.492430 |
308 | 0.770344 | -0.635648 | 3.289203 | -0.151866 |
434 | -0.594539 | -0.124445 | 0.535540 | 3.243883 |
454 | 0.196540 | 0.002230 | 1.150842 | -3.098229 |
475 | 1.327124 | 0.532669 | -0.285367 | 3.095290 |
604 | 1.154687 | 0.336886 | -0.767514 | 3.229107 |
659 | -3.076200 | -0.979556 | 0.653771 | 0.559905 |
排列和随机采样
利用 numpy.random.permutation
函数可以轻松实现对Series或DataFrame的列的排列工作(permuting,随机重排序)。通过需要排列的轴的长度调用 permutation,可产生一个表示新顺序的整数数组:
|
|
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 | 7 |
2 | 8 | 9 | 10 | 11 |
3 | 12 | 13 | 14 | 15 |
4 | 16 | 17 | 18 | 19 |
|
|
array([2, 1, 4, 3, 0])
然后就可以在基于iloc的索引操作或take函数中使用该数组了:
|
|
0 | 1 | 2 | 3 | |
---|---|---|---|---|
2 | 8 | 9 | 10 | 11 |
1 | 4 | 5 | 6 | 7 |
4 | 16 | 17 | 18 | 19 |
3 | 12 | 13 | 14 | 15 |
0 | 0 | 1 | 2 | 3 |
选取随机子集(不重复的)
|
|
0 | 1 | 2 | 3 | |
---|---|---|---|---|
3 | 12 | 13 | 14 | 15 |
1 | 4 | 5 | 6 | 7 |
2 | 8 | 9 | 10 | 11 |
选取随机子集(允许重复),不能操作 DateFrame
|
|
0 5
4 4
0 5
0 5
2 -1
3 6
3 6
3 6
3 6
0 5
dtype: int64
|
|
0 5
1 7
2 -1
3 6
4 4
dtype: int64
计算指标/哑变量
另一种常用于统计建模或机器学习的转换方式是:将分类变量(categorical variable)转换为“哑变量”或“指标矩阵”。
如果DataFrame的某一列中含有k个不同的值,则可以派生出一个k列矩阵或DataFrame(其值全为1和0)。pandas有一个get_dummies函数可以实现该功能(其实自己动手做一个也不难)。使用之前的一个DataFrame例子:
|
|
key | data1 | |
---|---|---|
0 | b | 0 |
1 | b | 1 |
2 | a | 2 |
3 | c | 3 |
4 | a | 4 |
5 | b | 5 |
|
|
a | b | c | |
---|---|---|---|
0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 |
2 | 1 | 0 | 0 |
3 | 0 | 0 | 1 |
4 | 1 | 0 | 0 |
5 | 0 | 1 | 0 |
有时候,你可能想给指标DataFrame的列加上一个前缀,以便能够跟其他数据进行合并。get_dummies的prefix参数可以实现该功能:
|
|
key_a | key_b | key_c | |
---|---|---|---|
0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 |
2 | 1 | 0 | 0 |
3 | 0 | 0 | 1 |
4 | 1 | 0 | 0 |
5 | 0 | 1 | 0 |
|
|
data1 | key_a | key_b | key_c | |
---|---|---|---|---|
0 | 0 | 0 | 1 | 0 |
1 | 1 | 0 | 1 | 0 |
2 | 2 | 1 | 0 | 0 |
3 | 3 | 0 | 0 | 1 |
4 | 4 | 1 | 0 | 0 |
5 | 5 | 0 | 1 | 0 |
如果DataFrame中的某行同属于多个分类,则事情就会有点复杂。看一下MovieLens 1M数据集
|
|
C:\ProgramData\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: ParserWarning: Falling back to the 'python' engine because the 'c' engine does not support regex separators (separators > 1 char and different from '\s+' are interpreted as regex); you can avoid this warning by specifying engine='python'.
movie_id | title | genres | |
---|---|---|---|
0 | 1 | Toy Story (1995) | Animation|Children's|Comedy |
1 | 2 | Jumanji (1995) | Adventure|Children's|Fantasy |
2 | 3 | Grumpier Old Men (1995) | Comedy|Romance |
3 | 4 | Waiting to Exhale (1995) | Comedy|Drama |
4 | 5 | Father of the Bride Part II (1995) | Comedy |
要为每个genre添加指标变量就需要做一些数据规整操作。首先,我们从数据集中抽取出不同的genre值:
|
|
array(['Animation', "Children's", 'Comedy', 'Adventure', 'Fantasy',
'Romance', 'Drama', 'Action', 'Crime', 'Thriller', 'Horror',
'Sci-Fi', 'Documentary', 'War', 'Musical', 'Mystery', 'Film-Noir',
'Western'], dtype=object)
构建指标DataFrame的方法之一是从一个全零DataFrame开始:
|
|
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]])
|
|
Animation | Children's | Comedy | Adventure | Fantasy | Romance | Drama | Action | Crime | Thriller | Horror | Sci-Fi | Documentary | War | Musical | Mystery | Film-Noir | Western | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
4 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
现在,迭代每一部电影,并将dummies各行的条目设为1。要这么做,我们使用dummies.columns来计算每个类型的列索引:
|
|
"Animation|Children's|Comedy"
|
|
['Animation', "Children's", 'Comedy']
|
|
array([0, 1, 2], dtype=int64)
根据索引,使用.iloc设定值:
|
|
将其与movies合并起来:
|
|
movie_id | title | genres | Genre_Animation | Genre_Children's | Genre_Comedy | Genre_Adventure | Genre_Fantasy | Genre_Romance | Genre_Drama | ... | Genre_Crime | Genre_Thriller | Genre_Horror | Genre_Sci-Fi | Genre_Documentary | Genre_War | Genre_Musical | Genre_Mystery | Genre_Film-Noir | Genre_Western | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Toy Story (1995) | Animation|Children's|Comedy | 1.0 | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 2 | Jumanji (1995) | Adventure|Children's|Fantasy | 0.0 | 1.0 | 0.0 | 1.0 | 1.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | 3 | Grumpier Old Men (1995) | Comedy|Romance | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 1.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 | 4 | Waiting to Exhale (1995) | Comedy|Drama | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
4 | 5 | Father of the Bride Part II (1995) | Comedy | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
5 rows × 21 columns
字符串操作
pandas 的矢量化字符串函数
|
|
Dave dave@google.com
Steve steve@gmail.com
Rob rob@gmail.com
Wes NaN
dtype: object
|
|
Dave False
Steve False
Rob False
Wes True
dtype: bool
通过 data.map
,所有字符串和正则表达式方法都能被应用于(传入lambda表达式或其他函数)各个值,但是如果存在 NA(null)
就会报错。为了解决这个问题,Series有一些能够跳过NA值的面向数组方法,进行字符串操作。通过Series的 str
属性即可访问这些方法。例如,我们可以通过 str.contains
检查各个电子邮件地址是否含有”gmail”:
|
|
Dave False
Steve True
Rob True
Wes NaN
dtype: object
也可以使用正则表达式,还可以加上任意re选项(如IGNORECASE):
|
|
Dave [(dave, google, com)]
Steve [(steve, gmail, com)]
Rob [(rob, gmail, com)]
Wes NaN
dtype: object
对字符串进行截取
|
|
Dave dave@
Steve steve
Rob rob@g
Wes NaN
dtype: object
更多的pandas字符串方法
数据规整:聚合、合并和重塑
层次化索引
层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。
|
|
a 1 1.280879
2 -0.233278
3 0.700301
b 1 0.115678
3 0.390445
c 1 -0.816532
2 -0.972933
d 2 2.053691
3 1.166844
dtype: float64
|
|
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])
对于一个层次化索引的对象,可以使用所谓的部分索引,使用它选取数据子集的操作更简单:
|
|
1 0.115678
3 0.390445
dtype: float64
|
|
b 1 0.115678
3 0.390445
c 1 -0.816532
2 -0.972933
dtype: float64
|
|
b 1 0.115678
3 0.390445
c 1 -0.816532
2 -0.972933
dtype: float64
在“内层”中进行选取
|
|
a -0.233278
c -0.972933
d 2.053691
dtype: float64
unstack方法将这段数据重新安排到一个DataFrame中:
|
|
1 | 2 | 3 | |
---|---|---|---|
a | 1.280879 | -0.233278 | 0.700301 |
b | 0.115678 | NaN | 0.390445 |
c | -0.816532 | -0.972933 | NaN |
d | NaN | 2.053691 | 1.166844 |
unstack的逆运算是stack:
|
|
a 1 1.280879
2 -0.233278
3 0.700301
b 1 0.115678
3 0.390445
c 1 -0.816532
2 -0.972933
d 2 2.053691
3 1.166844
dtype: float64
对于一个DataFrame,每条轴都可以有分层索引:
|
|
Ohio | Colorado | |||
---|---|---|---|---|
Green | Red | Green | ||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中:
|
|
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
有了部分列索引,因此可以轻松选取列分组:
|
|
color | Green | Red | |
---|---|---|---|
key1 | key2 | ||
a | 1 | 0 | 1 |
2 | 3 | 4 | |
b | 1 | 6 | 7 |
2 | 9 | 10 |
|
|
MultiIndex(levels=[['a', 'b'], [1, 2]],
codes=[[0, 0, 1, 1], [0, 1, 0, 1]],
names=['key1', 'key2'])
重排与分级排序
重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。
swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):
|
|
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
|
|
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key2 | key1 | |||
1 | a | 0 | 1 | 2 |
2 | a | 3 | 4 | 5 |
1 | b | 6 | 7 | 8 |
2 | b | 9 | 10 | 11 |
sort_index则根据单个级别中的值对数据进行排序。
|
|
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
b | 1 | 6 | 7 | 8 |
a | 2 | 3 | 4 | 5 |
b | 2 | 9 | 10 | 11 |
|
|
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
|
|
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key2 | key1 | |||
1 | a | 0 | 1 | 2 |
b | 6 | 7 | 8 | |
2 | a | 3 | 4 | 5 |
b | 9 | 10 | 11 |
根据级别汇总统计
许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。
|
|
state | Ohio | Colorado | ||
---|---|---|---|---|
color | Green | Red | Green | |
key1 | key2 | |||
a | 1 | 0 | 1 | 2 |
2 | 3 | 4 | 5 | |
b | 1 | 6 | 7 | 8 |
2 | 9 | 10 | 11 |
|
|
state | Ohio | Colorado | |
---|---|---|---|
color | Green | Red | Green |
key2 | |||
1 | 6 | 8 | 10 |
2 | 12 | 14 | 16 |
|
|
color | Green | Red | |
---|---|---|---|
key1 | key2 | ||
a | 1 | 2 | 1 |
2 | 8 | 4 | |
b | 1 | 14 | 7 |
2 | 20 | 10 |
使用DataFrame的列进行索引
人们经常想要将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成DataFrame的列。
DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:
|
|
a | b | c | d | |
---|---|---|---|---|
0 | 0 | 7 | one | 0 |
1 | 1 | 6 | one | 1 |
2 | 2 | 5 | one | 2 |
3 | 3 | 4 | two | 0 |
4 | 4 | 3 | two | 1 |
5 | 5 | 2 | two | 2 |
6 | 6 | 1 | two | 3 |
|
|
a | b | ||
---|---|---|---|
c | d | ||
one | 0 | 0 | 7 |
1 | 1 | 6 | |
2 | 2 | 5 | |
two | 0 | 3 | 4 |
1 | 4 | 3 | |
2 | 5 | 2 | |
3 | 6 | 1 |
默认情况下,那些列会从DataFrame中移除,但也可以将其保留下来:
|
|
a | b | c | d | ||
---|---|---|---|---|---|
c | d | ||||
one | 0 | 0 | 7 | one | 0 |
1 | 1 | 6 | one | 1 | |
2 | 2 | 5 | one | 2 | |
two | 0 | 3 | 4 | two | 0 |
1 | 4 | 3 | two | 1 | |
2 | 5 | 2 | two | 2 | |
3 | 6 | 1 | two | 3 |
reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:
|
|
c | d | a | b | |
---|---|---|---|---|
0 | one | 0 | 0 | 7 |
1 | one | 1 | 1 | 6 |
2 | one | 2 | 2 | 5 |
3 | two | 0 | 3 | 4 |
4 | two | 1 | 4 | 3 |
5 | two | 2 | 5 | 2 |
6 | two | 3 | 6 | 1 |
合并数据集
pandas对象中的数据可以通过一些方式进行合并:
- pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。SQL或其他关系型数据库的用户对此应该会比较熟悉,因为它实现的就是数据库的join操作。
- pandas.concat可以沿着一条轴将多个对象堆叠到一起。
- 实例方法combine_first可以将重复数据拼接在一起,用一个对象中的值填充另一个对象中的缺失值。
数据库风格的DataFrame合并
数据集的合并(merge)或连接(join)运算是通过一个或多个键将行连接起来的。这些运算是关系型数据库(基于SQL)的核心。pandas的merge函数是对数据应用这些算法的主要切入点。
|
|
key | data1 | |
---|---|---|
0 | b | 0 |
1 | b | 1 |
2 | a | 2 |
3 | c | 3 |
4 | a | 4 |
5 | a | 5 |
6 | b | 6 |
|
|
key | data2 | |
---|---|---|
0 | a | 0 |
1 | b | 1 |
2 | d | 2 |
如果没有指定关联的列,merge就会将重叠列的列名当做键。默认情况下,merge做的是“内连接”;结果中的键是交集。
|
|
key | data1 | data2 | |
---|---|---|---|
0 | b | 0 | 1 |
1 | b | 1 | 1 |
2 | b | 6 | 1 |
3 | a | 2 | 0 |
4 | a | 4 | 0 |
5 | a | 5 | 0 |
指明要用哪个列进行连接。
|
|
key | data1 | data2 | |
---|---|---|---|
0 | b | 0 | 1 |
1 | b | 1 | 1 |
2 | b | 6 | 1 |
3 | a | 2 | 0 |
4 | a | 4 | 0 |
5 | a | 5 | 0 |
如果两个对象的列名不同,也可以分别进行指定:
|
|
lkey | data1 | rkey | data2 | |
---|---|---|---|---|
0 | b | 0 | b | 1 |
1 | b | 1 | b | 1 |
2 | b | 6 | b | 1 |
3 | a | 2 | a | 0 |
4 | a | 4 | a | 0 |
5 | a | 5 | a | 0 |
指定连接方式
|
|
key | data1 | data2 | |
---|---|---|---|
0 | b | 0.0 | 1.0 |
1 | b | 1.0 | 1.0 |
2 | b | 6.0 | 1.0 |
3 | a | 2.0 | 0.0 |
4 | a | 4.0 | 0.0 |
5 | a | 5.0 | 0.0 |
6 | c | 3.0 | NaN |
7 | d | NaN | 2.0 |
要根据多个键进行合并,传入一个由列名组成的列表即可:
|
|
key1 | key2 | lval | |
---|---|---|---|
0 | foo | one | 1 |
1 | foo | two | 2 |
2 | bar | one | 3 |
|
|
key1 | key2 | rval | |
---|---|---|---|
0 | foo | one | 4 |
1 | foo | one | 5 |
2 | bar | one | 6 |
3 | bar | two | 7 |
|
|
key1 | key2 | lval | rval | |
---|---|---|---|---|
0 | foo | one | 1.0 | 4.0 |
1 | foo | one | 1.0 | 5.0 |
2 | foo | two | 2.0 | NaN |
3 | bar | one | 3.0 | 6.0 |
4 | bar | two | NaN | 7.0 |
对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(查看前面介绍的重命名轴标签),但merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:
|
|
key1 | key2_x | lval | key2_y | rval | |
---|---|---|---|---|---|
0 | foo | one | 1 | one | 4 |
1 | foo | one | 1 | one | 5 |
2 | foo | two | 2 | one | 4 |
3 | foo | two | 2 | one | 5 |
4 | bar | one | 3 | one | 6 |
5 | bar | one | 3 | two | 7 |
|
|
key1 | key2_left | lval | key2_right | rval | |
---|---|---|---|---|---|
0 | foo | one | 1 | one | 4 |
1 | foo | one | 1 | one | 5 |
2 | foo | two | 2 | one | 4 |
3 | foo | two | 2 | one | 5 |
4 | bar | one | 3 | one | 6 |
5 | bar | one | 3 | two | 7 |
merge函数的参数
索引上的合并
有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传入 left_index=True
或 right_index=True
(或两个都传)以说明索引应该被用作连接键:
|
|
key | value | |
---|---|---|
0 | a | 0 |
1 | b | 1 |
2 | a | 2 |
3 | a | 3 |
4 | b | 4 |
5 | c | 5 |
|
|
group_val | |
---|---|
a | 3.5 |
b | 7.0 |
|
|
key | value | group_val | |
---|---|---|---|
0 | a | 0 | 3.5 |
2 | a | 2 | 3.5 |
3 | a | 3 | 3.5 |
1 | b | 1 | 7.0 |
4 | b | 4 | 7.0 |
对于层次化索引的数据,事情就有点复杂了,因为索引的合并默认是多键合并:
|
|
key1 | key2 | data | |
---|---|---|---|
0 | Ohio | 2000 | 0.0 |
1 | Ohio | 2001 | 1.0 |
2 | Ohio | 2002 | 2.0 |
3 | Nevada | 2001 | 3.0 |
4 | Nevada | 2002 | 4.0 |
|
|
event1 | event2 | ||
---|---|---|---|
Nevada | 2001 | 0 | 1 |
2000 | 2 | 3 | |
Ohio | 2000 | 4 | 5 |
2000 | 6 | 7 | |
2001 | 8 | 9 | |
2002 | 10 | 11 |
这种情况下,你必须以列表的形式指明用作合并键的多个列
|
|
key1 | key2 | data | event1 | event2 | |
---|---|---|---|---|---|
0 | Ohio | 2000 | 0.0 | 4 | 5 |
0 | Ohio | 2000 | 0.0 | 6 | 7 |
1 | Ohio | 2001 | 1.0 | 8 | 9 |
2 | Ohio | 2002 | 2.0 | 10 | 11 |
3 | Nevada | 2001 | 3.0 | 0 | 1 |
同时使用合并双方的索引也没问题:
|
|
Ohio | Nevada | |
---|---|---|
a | 1.0 | 2.0 |
c | 3.0 | 4.0 |
e | 5.0 | 6.0 |
|
|
Missouri | Alabama | |
---|---|---|
b | 7.0 | 8.0 |
c | 9.0 | 10.0 |
d | 11.0 | 12.0 |
e | 13.0 | 14.0 |
|
|
Ohio | Nevada | Missouri | Alabama | |
---|---|---|---|---|
a | 1.0 | 2.0 | NaN | NaN |
b | NaN | NaN | 7.0 | 8.0 |
c | 3.0 | 4.0 | 9.0 | 10.0 |
d | NaN | NaN | 11.0 | 12.0 |
e | 5.0 | 6.0 | 13.0 | 14.0 |
DataFrame还有一个便捷的join实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的DataFrame对象,但要求没有重叠的列。
|
|
Ohio | Nevada | Missouri | Alabama | |
---|---|---|---|---|
a | 1.0 | 2.0 | NaN | NaN |
b | NaN | NaN | 7.0 | 8.0 |
c | 3.0 | 4.0 | 9.0 | 10.0 |
d | NaN | NaN | 11.0 | 12.0 |
e | 5.0 | 6.0 | 13.0 | 14.0 |
DataFrame的join方法默认使用的是左连接,保留左边表的行索引。它还支持在调用的DataFrame的列上,连接传递的DataFrame索引:
|
|
key | value | group_val | |
---|---|---|---|
0 | a | 0 | 3.5 |
1 | b | 1 | 7.0 |
2 | a | 2 | 3.5 |
3 | a | 3 | 3.5 |
4 | b | 4 | 7.0 |
5 | c | 5 | NaN |
对于简单的索引合并,你还可以向join传入一组DataFrame
|
|
New York | Oregon | |
---|---|---|
a | 7.0 | 8.0 |
c | 9.0 | 10.0 |
e | 11.0 | 12.0 |
f | 16.0 | 17.0 |
|
|
Ohio | Nevada | Missouri | Alabama | New York | Oregon | |
---|---|---|---|---|---|---|
a | 1.0 | 2.0 | NaN | NaN | 7.0 | 8.0 |
c | 3.0 | 4.0 | 9.0 | 10.0 | 9.0 | 10.0 |
e | 5.0 | 6.0 | 13.0 | 14.0 | 11.0 | 12.0 |
轴向连接
另一种数据合并运算也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。NumPy的concatenate函数可以用NumPy数组来做:
|
|
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
|
|
array([[ 0, 1, 2, 3, 0, 1, 2, 3],
[ 4, 5, 6, 7, 4, 5, 6, 7],
[ 8, 9, 10, 11, 8, 9, 10, 11]])
对于pandas对象(如Series和DataFrame),带有标签的轴使你能够进一步推广数组的连接运算。
pandas的concat函数提供了一种能够解决这些问题的可靠方式。
|
|
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
默认情况下,concat是在axis=0上工作的,最终产生一个新的Series。如果传入axis=1,则结果就会变成一个DataFrame(axis=1是列):
|
|
/usr/local/lib/python3.6/site-packages/ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
0 | 1 | 2 | |
---|---|---|---|
a | 0.0 | NaN | NaN |
b | 1.0 | NaN | NaN |
c | NaN | 2.0 | NaN |
d | NaN | 3.0 | NaN |
e | NaN | 4.0 | NaN |
f | NaN | NaN | 5.0 |
g | NaN | NaN | 6.0 |
这种情况下,另外的轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。传入join=’inner’即可得到它们的交集:
|
|
a 0
b 1
f 5
g 6
dtype: int64
|
|
/usr/local/lib/python3.6/site-packages/ipykernel_launcher.py:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass 'sort=False'.
To retain the current behavior and silence the warning, pass 'sort=True'.
"""Entry point for launching an IPython kernel.
0 | 1 | |
---|---|---|
a | 0.0 | 0 |
b | 1.0 | 1 |
f | NaN | 5 |
g | NaN | 6 |
|
|
0 | 1 | |
---|---|---|
a | 0 | 0 |
b | 1 | 1 |
通过 join_axes 指定要在其它轴上使用的索引:
|
|
0 | 1 | |
---|---|---|
a | 0.0 | 0.0 |
c | NaN | NaN |
b | 1.0 | 1.0 |
e | NaN | NaN |
不过有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引。使用keys参数即可达到这个目的:
|
|
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: int64
|
|
a | b | f | g | |
---|---|---|---|---|
one | 0.0 | 1.0 | NaN | NaN |
two | 0.0 | 1.0 | NaN | NaN |
three | NaN | NaN | 5.0 | 6.0 |
如果沿着 axis=1
对Series进行合并,则keys就会成为DataFrame的列头:
|
|
one | two | three | |
---|---|---|---|
a | 0.0 | NaN | NaN |
b | 1.0 | NaN | NaN |
c | NaN | 2.0 | NaN |
d | NaN | 3.0 | NaN |
e | NaN | 4.0 | NaN |
f | NaN | NaN | 5.0 |
g | NaN | NaN | 6.0 |
同样的逻辑也适用于DataFrame对象:
|
|
one | two | |
---|---|---|
a | 0 | 1 |
b | 2 | 3 |
c | 4 | 5 |
|
|
three | four | |
---|---|---|
a | 5 | 6 |
c | 7 | 8 |
|
|
level1 | level2 | |||
---|---|---|---|---|
one | two | three | four | |
a | 0 | 1 | 5.0 | 6.0 |
b | 2 | 3 | NaN | NaN |
c | 4 | 5 | 7.0 | 8.0 |
如果传入的不是列表而是一个字典,则字典的键就会被当做keys选项的值:
|
|
level1 | level2 | |||
---|---|---|---|---|
one | two | three | four | |
a | 0 | 1 | 5.0 | 6.0 |
b | 2 | 3 | NaN | NaN |
c | 4 | 5 | 7.0 | 8.0 |
我们可以用names参数命名创建的轴级别:
|
|
upper | level1 | level2 | ||
---|---|---|---|---|
lower | one | two | three | four |
a | 0 | 1 | 5.0 | 6.0 |
b | 2 | 3 | NaN | NaN |
c | 4 | 5 | 7.0 | 8.0 |
DataFrame 在连接的时候会使用原来的索引,可以通过 ignore_index=True
来放弃使用
|
|
a | b | c | d | |
---|---|---|---|---|
0 | -1.044321 | -0.049004 | 0.026555 | -0.315565 |
1 | -0.085761 | 0.873464 | -1.368797 | 0.302554 |
2 | 0.277496 | -0.570469 | -0.606294 | 1.253497 |
|
|
b | d | a | |
---|---|---|---|
0 | 0.343570 | -0.310123 | -0.379563 |
1 | -1.083807 | -1.480836 | -1.255112 |
|
|
a | b | c | d | |
---|---|---|---|---|
0 | -1.044321 | -0.049004 | 0.026555 | -0.315565 |
1 | -0.085761 | 0.873464 | -1.368797 | 0.302554 |
2 | 0.277496 | -0.570469 | -0.606294 | 1.253497 |
0 | -0.379563 | 0.343570 | NaN | -0.310123 |
1 | -1.255112 | -1.083807 | NaN | -1.480836 |
|
|
a | b | c | d | |
---|---|---|---|---|
0 | -1.044321 | -0.049004 | 0.026555 | -0.315565 |
1 | -0.085761 | 0.873464 | -1.368797 | 0.302554 |
2 | 0.277496 | -0.570469 | -0.606294 | 1.253497 |
3 | -0.379563 | 0.343570 | NaN | -0.310123 |
4 | -1.255112 | -1.083807 | NaN | -1.480836 |
concat函数的参数
合并重叠数据
还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,你可能有索引全部或部分重叠的两个数据集。举个有启发性的例子,我们使用NumPy的where函数,它表示一种等价于面向数组的if-else:
就是用一个数据组合来填充另一个数据组合的空值
|
|
f NaN
e 2.5
d NaN
c 3.5
b 4.5
a NaN
dtype: float64
|
|
f 0.0
e 1.0
d 2.0
c 3.0
b 4.0
a NaN
dtype: float64
|
|
array([0. , 2.5, 2. , 3.5, 4.5, nan])
Series 和 DataFrame 有一个 combine_first 方法,实现的也是一样的功能,还带有pandas的数据对齐:
|
|
a NaN
b 4.5
c 3.0
d 2.0
e 1.0
f 0.0
dtype: float64
|
|
a | b | c | |
---|---|---|---|
0 | 1.0 | NaN | 2 |
1 | NaN | 2.0 | 6 |
2 | 5.0 | NaN | 10 |
3 | NaN | 6.0 | 14 |
|
|
a | b | |
---|---|---|
0 | 5.0 | NaN |
1 | 4.0 | 3.0 |
2 | NaN | 4.0 |
3 | 3.0 | 6.0 |
4 | 7.0 | 8.0 |
|
|
a | b | c | |
---|---|---|---|
0 | 1.0 | NaN | 2.0 |
1 | 4.0 | 2.0 | 6.0 |
2 | 5.0 | 4.0 | 10.0 |
3 | 3.0 | 6.0 | 14.0 |
4 | 7.0 | 8.0 | NaN |
重塑和轴向旋转
有许多用于重新排列表格型数据的基础运算。这些函数也称作重塑(reshape)或轴向旋转(pivot)运算。
重塑层次化索引
层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:
- stack:将数据的列“旋转”为行。
- unstack:将数据的行“旋转”为列。
|
|
number | one | two | three |
---|---|---|---|
state | |||
Ohio | 0 | 1 | 2 |
Colorado | 3 | 4 | 5 |
对该数据使用stack方法即可将列转换为行,得到一个Series:
|
|
state number
Ohio one 0
two 1
three 2
Colorado one 3
two 4
three 5
dtype: int64
对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame:
|
|
number | one | two | three |
---|---|---|---|
state | |||
Ohio | 0 | 1 | 2 |
Colorado | 3 | 4 | 5 |
默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其它级别进行unstack操作:
|
|
state | Ohio | Colorado |
---|---|---|
number | ||
one | 0 | 3 |
two | 1 | 4 |
three | 2 | 5 |
|
|
state | Ohio | Colorado |
---|---|---|
number | ||
one | 0 | 3 |
two | 1 | 4 |
three | 2 | 5 |
如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据:
|
|
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: int64
|
|
a | b | c | d | e | |
---|---|---|---|---|---|
one | 0.0 | 1.0 | 2.0 | 3.0 | NaN |
two | NaN | NaN | 4.0 | 5.0 | 6.0 |
|
|
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64
|
|
one a 0.0
b 1.0
c 2.0
d 3.0
e NaN
two a NaN
b NaN
c 4.0
d 5.0
e 6.0
dtype: float64
在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别:
|
|
side | left | right | |
---|---|---|---|
state | number | ||
Ohio | one | 0 | 5 |
two | 1 | 6 | |
three | 2 | 7 | |
Colorado | one | 3 | 8 |
two | 4 | 9 | |
three | 5 | 10 |
|
|
side | left | right | ||
---|---|---|---|---|
state | Ohio | Colorado | Ohio | Colorado |
number | ||||
one | 0 | 3 | 5 | 8 |
two | 1 | 4 | 6 | 9 |
three | 2 | 5 | 7 | 10 |
当调用stack,我们可以指明轴的名字:
|
|
state | Colorado | Ohio | |
---|---|---|---|
number | side | ||
one | left | 3 | 0 |
right | 8 | 5 | |
two | left | 4 | 1 |
right | 9 | 6 | |
three | left | 5 | 2 |
right | 10 | 7 |
将“长格式”旋转为“宽格式”
多个时间序列数据通常是以所谓的“长格式”(long)或“堆叠格式”(stacked)存储在数据库和CSV中的。
|
|
year | quarter | realgdp | realcons | realinv | realgovt | realdpi | cpi | m1 | tbilrate | unemp | pop | infl | realint | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1959.0 | 1.0 | 2710.349 | 1707.4 | 286.898 | 470.045 | 1886.9 | 28.98 | 139.7 | 2.82 | 5.8 | 177.146 | 0.00 | 0.00 |
1 | 1959.0 | 2.0 | 2778.801 | 1733.7 | 310.859 | 481.301 | 1919.7 | 29.15 | 141.7 | 3.08 | 5.1 | 177.830 | 2.34 | 0.74 |
2 | 1959.0 | 3.0 | 2775.488 | 1751.8 | 289.226 | 491.260 | 1916.4 | 29.35 | 140.5 | 3.82 | 5.3 | 178.657 | 2.74 | 1.09 |
3 | 1959.0 | 4.0 | 2785.204 | 1753.7 | 299.356 | 484.052 | 1931.3 | 29.37 | 140.0 | 4.33 | 5.6 | 179.386 | 0.27 | 4.06 |
4 | 1960.0 | 1.0 | 2847.699 | 1770.5 | 331.722 | 462.199 | 1955.5 | 29.54 | 139.6 | 3.50 | 5.2 | 180.007 | 2.31 | 1.19 |
|
|
PeriodIndex(['1959Q1', '1959Q2', '1959Q3', '1959Q4', '1960Q1', '1960Q2',
'1960Q3', '1960Q4', '1961Q1', '1961Q2',
...
'2007Q2', '2007Q3', '2007Q4', '2008Q1', '2008Q2', '2008Q3',
'2008Q4', '2009Q1', '2009Q2', '2009Q3'],
dtype='period[Q-DEC]', name='date', length=203, freq='Q-DEC')
|
|
Index(['realgdp', 'infl', 'unemp'], dtype='object', name='item')
|
|
item | realgdp | infl | unemp |
---|---|---|---|
0 | 2710.349 | 0.00 | 5.8 |
1 | 2778.801 | 2.34 | 5.1 |
2 | 2775.488 | 2.74 | 5.3 |
3 | 2785.204 | 0.27 | 5.6 |
4 | 2847.699 | 2.31 | 5.2 |
|
|
item | realgdp | infl | unemp |
---|---|---|---|
date | |||
1959-03-31 23:59:59.999999999 | 2710.349 | 0.00 | 5.8 |
1959-06-30 23:59:59.999999999 | 2778.801 | 2.34 | 5.1 |
1959-09-30 23:59:59.999999999 | 2775.488 | 2.74 | 5.3 |
1959-12-31 23:59:59.999999999 | 2785.204 | 0.27 | 5.6 |
1960-03-31 23:59:59.999999999 | 2847.699 | 2.31 | 5.2 |
这就是多个时间序列(或者其它带有两个或多个键的可观察数据,这里,我们的键是date和item)的长格式。表中的每行代表一次观察。
|
|
date | item | value | |
---|---|---|---|
0 | 1959-03-31 23:59:59.999999999 | realgdp | 2710.349 |
1 | 1959-03-31 23:59:59.999999999 | infl | 0.000 |
2 | 1959-03-31 23:59:59.999999999 | unemp | 5.800 |
3 | 1959-06-30 23:59:59.999999999 | realgdp | 2778.801 |
4 | 1959-06-30 23:59:59.999999999 | infl | 2.340 |
|
|
date item
1959-03-31 23:59:59.999999999 realgdp 2710.349
infl 0.000
unemp 5.800
1959-06-30 23:59:59.999999999 realgdp 2778.801
infl 2.340
dtype: float64
|
|
date | item | 0 | |
---|---|---|---|
0 | 1959-03-31 23:59:59.999999999 | realgdp | 2710.349 |
1 | 1959-03-31 23:59:59.999999999 | infl | 0.000 |
2 | 1959-03-31 23:59:59.999999999 | unemp | 5.800 |
3 | 1959-06-30 23:59:59.999999999 | realgdp | 2778.801 |
4 | 1959-06-30 23:59:59.999999999 | infl | 2.340 |
关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加,item列中的值的种类能够增加。在前面的例子中,date和item通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。有的情况下,使用这样的数据会很麻烦,你可能会更喜欢DataFrame,不同的item值分别形成一列,date列中的时间戳则用作索引。DataFrame的pivot方法完全可以实现这个转换:
|
|
item | infl | realgdp | unemp |
---|---|---|---|
date | |||
1959-03-31 23:59:59.999999999 | 0.00 | 2710.349 | 5.8 |
1959-06-30 23:59:59.999999999 | 2.34 | 2778.801 | 5.1 |
1959-09-30 23:59:59.999999999 | 2.74 | 2775.488 | 5.3 |
1959-12-31 23:59:59.999999999 | 0.27 | 2785.204 | 5.6 |
1960-03-31 23:59:59.999999999 | 2.31 | 2847.699 | 5.2 |
前两个传递的值分别用作行和列索引,最后一个可选值则是用于填充DataFrame的数据列。假设有两个需要同时重塑的数据列:
如果忽略最后一个参数,得到的DataFrame就会带有层次化的列:
|
|
value | |||
---|---|---|---|
item | infl | realgdp | unemp |
date | |||
1959-03-31 23:59:59.999999999 | 0.00 | 2710.349 | 5.8 |
1959-06-30 23:59:59.999999999 | 2.34 | 2778.801 | 5.1 |
1959-09-30 23:59:59.999999999 | 2.74 | 2775.488 | 5.3 |
1959-12-31 23:59:59.999999999 | 0.27 | 2785.204 | 5.6 |
1960-03-31 23:59:59.999999999 | 2.31 | 2847.699 | 5.2 |
|
|
date | item | value | value2 | |
---|---|---|---|---|
0 | 1959-03-31 23:59:59.999999999 | realgdp | 2710.349 | 0.363946 |
1 | 1959-03-31 23:59:59.999999999 | infl | 0.000 | -0.287075 |
2 | 1959-03-31 23:59:59.999999999 | unemp | 5.800 | 0.144127 |
3 | 1959-06-30 23:59:59.999999999 | realgdp | 2778.801 | -0.491877 |
4 | 1959-06-30 23:59:59.999999999 | infl | 2.340 | 0.725936 |
|
|
value | value2 | |||||
---|---|---|---|---|---|---|
item | infl | realgdp | unemp | infl | realgdp | unemp |
date | ||||||
1959-03-31 23:59:59.999999999 | 0.00 | 2710.349 | 5.8 | -0.287075 | 0.363946 | 0.144127 |
1959-06-30 23:59:59.999999999 | 2.34 | 2778.801 | 5.1 | 0.725936 | -0.491877 | 0.026073 |
1959-09-30 23:59:59.999999999 | 2.74 | 2775.488 | 5.3 | 1.025040 | 0.144513 | -1.524801 |
1959-12-31 23:59:59.999999999 | 0.27 | 2785.204 | 5.6 | 0.530287 | -1.027258 | -0.158004 |
1960-03-31 23:59:59.999999999 | 2.31 | 2847.699 | 5.2 | -0.476289 | 0.037717 | 1.186384 |
|
|
value | value2 | |||||
---|---|---|---|---|---|---|
item | infl | realgdp | unemp | infl | realgdp | unemp |
date | ||||||
1959-03-31 23:59:59.999999999 | 0.00 | 2710.349 | 5.8 | -0.287075 | 0.363946 | 0.144127 |
1959-06-30 23:59:59.999999999 | 2.34 | 2778.801 | 5.1 | 0.725936 | -0.491877 | 0.026073 |
1959-09-30 23:59:59.999999999 | 2.74 | 2775.488 | 5.3 | 1.025040 | 0.144513 | -1.524801 |
1959-12-31 23:59:59.999999999 | 0.27 | 2785.204 | 5.6 | 0.530287 | -1.027258 | -0.158004 |
1960-03-31 23:59:59.999999999 | 2.31 | 2847.699 | 5.2 | -0.476289 | 0.037717 | 1.186384 |
pivot其实就是用set_index创建层次化索引,再用unstack重塑:
|
|
value | value2 | |||||
---|---|---|---|---|---|---|
item | infl | realgdp | unemp | infl | realgdp | unemp |
date | ||||||
1959-03-31 23:59:59.999999999 | 0.00 | 2710.349 | 5.8 | -0.287075 | 0.363946 | 0.144127 |
1959-06-30 23:59:59.999999999 | 2.34 | 2778.801 | 5.1 | 0.725936 | -0.491877 | 0.026073 |
1959-09-30 23:59:59.999999999 | 2.74 | 2775.488 | 5.3 | 1.025040 | 0.144513 | -1.524801 |
1959-12-31 23:59:59.999999999 | 0.27 | 2785.204 | 5.6 | 0.530287 | -1.027258 | -0.158004 |
1960-03-31 23:59:59.999999999 | 2.31 | 2847.699 | 5.2 | -0.476289 | 0.037717 | 1.186384 |
将“宽格式”旋转为“长格式”
旋转DataFrame的逆运算是pandas.melt。它不是将一列转换到多个新的DataFrame,而是合并多个列成为一个,产生一个比输入长的DataFrame。
|
|
key | A | B | C | |
---|---|---|---|---|
0 | foo | 1 | 4 | 7 |
1 | bar | 2 | 5 | 8 |
2 | baz | 3 | 6 | 9 |
key列可能是分组指标,其它的列是数据值。当使用pandas.melt,我们必须指明哪些列是分组指标。下面使用key作为唯一的分组指标:
|
|
key | variable | value | |
---|---|---|---|
0 | foo | A | 1 |
1 | bar | A | 2 |
2 | baz | A | 3 |
3 | foo | B | 4 |
4 | bar | B | 5 |
5 | baz | B | 6 |
6 | foo | C | 7 |
7 | bar | C | 8 |
8 | baz | C | 9 |
使用pivot,可以重塑回原来的样子:
|
|
variable | A | B | C |
---|---|---|---|
key | |||
bar | 2 | 5 | 8 |
baz | 3 | 6 | 9 |
foo | 1 | 4 | 7 |
因为pivot的结果从列创建了一个索引,用作行标签,我们可以使用reset_index将数据移回列:
|
|
variable | key | A | B | C |
---|---|---|---|---|
0 | bar | 2 | 5 | 8 |
1 | baz | 3 | 6 | 9 |
2 | foo | 1 | 4 | 7 |
还可以指定列的子集,作为值的列:
|
|
key | variable | value | |
---|---|---|---|
0 | foo | A | 1 |
1 | bar | A | 2 |
2 | baz | A | 3 |
3 | foo | B | 4 |
4 | bar | B | 5 |
5 | baz | B | 6 |
pandas.melt也可以不用分组指标:
|
|
variable | value | |
---|---|---|
0 | A | 1 |
1 | A | 2 |
2 | A | 3 |
3 | B | 4 |
4 | B | 5 |
5 | B | 6 |
6 | C | 7 |
7 | C | 8 |
8 | C | 9 |
|
|
variable | value | |
---|---|---|
0 | key | foo |
1 | key | bar |
2 | key | baz |
3 | A | 1 |
4 | A | 2 |
5 | A | 3 |
6 | B | 4 |
7 | B | 5 |
8 | B | 6 |
数据聚合与分组运算
关系型数据库和SQL(Structured Query Language,结构化查询语言)能够如此流行的原因之一就是其能够方便地对数据进行连接、过滤、转换和聚合。但是,像SQL这样的查询语言所能执行的分组运算的种类很有限。在本章中你将会看到,由于Python和pandas强大的表达能力,我们可以执行复杂得多的分组运算(利用任何可以接受pandas对象或NumPy数组的函数)。
GroupBy机制
Hadley Wickham(许多热门R语言包的作者)创造了一个用于表示分组运算的术语”split-apply-combine”(拆分-应用-合并)。第一个阶段,pandas对象(无论是Series、DataFrame还是其他的)中的数据会根据你所提供的一个或多个键被拆分(split)为多组。拆分操作是在对象的特定轴上执行的。例如,DataFrame可以在其行(axis=0)或列(axis=1)上进行分组。然后,将一个函数应用(apply)到各个分组并产生一个新值。最后,所有这些函数的执行结果会被合并(combine)到最终的结果对象中。结果对象的形式一般取决于数据上所执行的操作。图10-1大致说明了一个简单的分组聚合过程。
|
|
key1 | key2 | data1 | data2 | |
---|---|---|---|---|
0 | a | one | 1.192962 | -1.675544 |
1 | a | two | 1.295617 | -0.621154 |
2 | b | one | 2.506938 | -0.954289 |
3 | b | two | -0.503276 | -0.698834 |
4 | a | one | -0.204452 | 0.767441 |
假设你想要按key1进行分组,并计算data1列的平均值。实现该功能的方式有很多,而我们这里要用的是:访问data1,并根据key1调用groupby:
|
|
<pandas.core.groupby.groupby.SeriesGroupBy object at 0x00000000080736D8>
变量 grouped
是一个 GroupBy
对象。它实际上还没有进行任何计算,只是含有一些有关分组键 df['key1']
的中间数据而已。换句话说,该对象已经有了接下来对各分组执行运算所需的一切信息。例如,我们可以调用 GroupBy
的 mean
方法来计算分组平均值:
|
|
key1
a 0.761376
b 1.001831
Name: data1, dtype: float64
数据(Series)根据分组键进行了聚合,产生了一个新的Series,其索引为key1列中的唯一值。之所以结果中索引的名称为key1,是因为原始DataFrame的列df[‘key1’]就叫这个名字。
使用多个分组字段
|
|
key1 key2
a one 0.494255
two 1.295617
b one 2.506938
two -0.503276
Name: data1, dtype: float64
这里,我通过两个键对数据进行了分组,得到的Series具有一个层次化索引(由唯一的键对组成)
|
|
key2 | one | two |
---|---|---|
key1 | ||
a | 0.494255 | 1.295617 |
b | 2.506938 | -0.503276 |
分组键可以是任何长度适当 (等于要分组数据的长度)的数组:
|
|
California 2005 1.295617
2006 2.506938
Ohio 2005 0.344843
2006 -0.204452
Name: data1, dtype: float64
通常,分组信息就位于相同的要处理DataFrame中。这里,你还可以将列名(可以是字符串、数字或其他Python对象)用作分组键:
|
|
data1 | data2 | |
---|---|---|
key1 | ||
a | 0.761376 | -0.509753 |
b | 1.001831 | -0.826561 |
|
|
data1 | data2 | ||
---|---|---|---|
key1 | key2 | ||
a | one | 0.494255 | -0.454052 |
two | 1.295617 | -0.621154 | |
b | one | 2.506938 | -0.954289 |
two | -0.503276 | -0.698834 |
你可能已经注意到了,第一个例子在执行df.groupby(‘key1’).mean()时,结果中没有key2列。这是因为df[‘key2’]不是数值数据(俗称“麻烦列”),所以被从结果中排除了。默认情况下,所有数值列都会被聚合,虽然有时可能会被过滤为一个子集,稍后就会碰到。
无论你准备拿 groupby 做什么,都有可能会用到 GroupBy 的 size 方法,它可以返回一个含有分组大小的 Series:
|
|
key1 key2
a one 2
two 1
b one 1
two 1
dtype: int64
注意,任何分组关键词中的缺失值,都会被从结果中除去。
对分组进行迭代
GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)。
|
|
key1 | key2 | data1 | data2 | |
---|---|---|---|---|
0 | a | one | 1.192962 | -1.675544 |
1 | a | two | 1.295617 | -0.621154 |
2 | b | one | 2.506938 | -0.954289 |
3 | b | two | -0.503276 | -0.698834 |
4 | a | one | -0.204452 | 0.767441 |
|
|
a
key1 key2 data1 data2
0 a one 1.192962 -1.675544
1 a two 1.295617 -0.621154
4 a one -0.204452 0.767441
b
key1 key2 data1 data2
2 b one 2.506938 -0.954289
3 b two -0.503276 -0.698834
对于多重键的情况,元组的第一个元素将会是由键值组成的元组:
|
|
('a', 'one')
key1 key2 data1 data2
0 a one 1.192962 -1.675544
4 a one -0.204452 0.767441
('a', 'two')
key1 key2 data1 data2
1 a two 1.295617 -0.621154
('b', 'one')
key1 key2 data1 data2
2 b one 2.506938 -0.954289
('b', 'two')
key1 key2 data1 data2
3 b two -0.503276 -0.698834
你可以对这些数据片段做任何操作。有一个你可能会觉得有用的运算:将这些数据片段做成一个字典:
|
|
key1 | key2 | data1 | data2 | |
---|---|---|---|---|
2 | b | one | 2.506938 | -0.954289 |
3 | b | two | -0.503276 | -0.698834 |
groupby默认是在 axis=0
上进行分组的,通过设置也可以在其他任何轴上进行分组。拿上面例子中的df来说,我们可以根据dtype对列进行分组:
|
|
key1 object
key2 object
data1 float64
data2 float64
dtype: object
|
|
float64
data1 data2
0 1.192962 -1.675544
1 1.295617 -0.621154
2 2.506938 -0.954289
3 -0.503276 -0.698834
4 -0.204452 0.767441
object
key1 key2
0 a one
1 a two
2 b one
3 b two
4 a one
选取一列或列的子集
对于由DataFrame产生的GroupBy对象,如果用一个(单个字符串)或一组(字符串数组)列名对其进行索引,就能实现选取部分列进行聚合的目的。
是以下代码的语法糖:
尤其对于大数据集,很可能只需要对部分列进行聚合。
例如,在前面那个数据集中,如果只需计算data2列的平均值并以DataFrame形式得到结果,可以这样写:
|
|
data2 | ||
---|---|---|
key1 | key2 | |
a | one | -0.454052 |
two | -0.621154 | |
b | one | -0.954289 |
two | -0.698834 |
|
|
key1 key2
a one -0.454052
two -0.621154
b one -0.954289
two -0.698834
Name: data2, dtype: float64
通过字典或Series进行分组
|
|
a | b | c | d | e | |
---|---|---|---|---|---|
Joe | 1.259713 | -0.377088 | 0.520075 | -0.881195 | 0.158433 |
Steve | -1.226603 | 0.648477 | -0.317307 | 0.012993 | 0.584107 |
Wes | -1.811950 | NaN | NaN | -0.576653 | 0.362209 |
Jim | 0.951494 | 0.253198 | -0.386507 | 1.172929 | -1.755465 |
Travis | 1.107800 | 0.297369 | -0.279916 | 1.512150 | 0.755150 |
假设已知列的分组关系,并希望根据分组计算列的和:
|
|
将这个字典传给groupby,来构造数组,但我们可以直接传递字典(我包含了键“f”来强调,存在未使用的分组键是可以的):
|
|
blue | red | |
---|---|---|
Joe | -0.361120 | 1.041059 |
Steve | -0.304314 | 0.005981 |
Wes | -0.576653 | -1.449741 |
Jim | 0.786422 | -0.550774 |
Travis | 1.232234 | 2.160319 |
Series也有同样的功能,它可以被看做一个固定大小的映射:
|
|
a red
b red
c blue
d blue
e red
f orange
dtype: object
|
|
blue | red | |
---|---|---|
Joe | 2 | 3 |
Steve | 2 | 3 |
Wes | 1 | 2 |
Jim | 2 | 3 |
Travis | 2 | 3 |
通过函数进行分组
比起使用字典或Series,使用Python函数是一种更原生的方法定义分组映射。任何被当做分组键的函数都会在各个索引值上被调用一次,其返回值就会被用作分组名称。
|
|
a | b | c | d | e | |
---|---|---|---|---|---|
Joe | 1.259713 | -0.377088 | 0.520075 | -0.881195 | 0.158433 |
Steve | -1.226603 | 0.648477 | -0.317307 | 0.012993 | 0.584107 |
Wes | -1.811950 | NaN | NaN | -0.576653 | 0.362209 |
Jim | 0.951494 | 0.253198 | -0.386507 | 1.172929 | -1.755465 |
Travis | 1.107800 | 0.297369 | -0.279916 | 1.512150 | 0.755150 |
其索引值为人的名字。你可以计算一个字符串长度的数组,更简单的方法是传入 len 函数:
|
|
a | b | c | d | e | |
---|---|---|---|---|---|
3 | 0.399257 | -0.123890 | 0.133568 | -0.284919 | -1.234823 |
5 | -1.226603 | 0.648477 | -0.317307 | 0.012993 | 0.584107 |
6 | 1.107800 | 0.297369 | -0.279916 | 1.512150 | 0.755150 |
将函数跟数组、列表、字典、Series混合使用也不是问题,因为任何东西在内部都会被转换为数组:
|
|
a | b | c | d | e | ||
---|---|---|---|---|---|---|
3 | one | -1.811950 | -0.377088 | 0.520075 | -0.881195 | 0.158433 |
two | 0.951494 | 0.253198 | -0.386507 | 1.172929 | -1.755465 | |
5 | one | -1.226603 | 0.648477 | -0.317307 | 0.012993 | 0.584107 |
6 | two | 1.107800 | 0.297369 | -0.279916 | 1.512150 | 0.755150 |
根据索引级别分组
层次化索引数据集最方便的地方就在于它能够根据轴索引的一个级别进行聚合:
|
|
cty | US | JP | |||
---|---|---|---|---|---|
tenor | 1 | 3 | 5 | 1 | 3 |
0 | -0.676690 | -0.294463 | 0.275278 | -0.315009 | -0.454633 |
1 | -1.509024 | 0.474617 | -0.969700 | -0.043906 | -1.237097 |
2 | 0.379676 | -0.577742 | 1.084988 | 0.499930 | 0.373462 |
3 | 1.097124 | -0.437426 | 0.725242 | -1.646882 | 0.571528 |
要根据级别分组,使用 level 关键字传递级别序号或名字:
|
|
cty | JP | US |
---|---|---|
0 | 2 | 3 |
1 | 2 | 3 |
2 | 2 | 3 |
3 | 2 | 3 |
数据聚合
聚合指的是任何能够从数组产生标量值的数据转换过程。之前的例子已经用过一些,比如mean、count、min以及sum等。
常见的聚合运算
|
|
key1 | key2 | data1 | data2 | |
---|---|---|---|---|
0 | a | one | 1.192962 | -1.675544 |
1 | a | two | 1.295617 | -0.621154 |
2 | b | one | 2.506938 | -0.954289 |
3 | b | two | -0.503276 | -0.698834 |
4 | a | one | -0.204452 | 0.767441 |
|
|
如果要使用你自己的聚合函数,只需将其传入 aggregate 或 agg 方法即可:
|
|
data1 | data2 | |
---|---|---|
key1 | ||
a | 1.500069 | 2.442985 |
b | 3.010214 | 0.255455 |
获取分组后的描述信息
|
|
data1 | data2 | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | mean | std | min | 25% | 50% | 75% | max | count | mean | std | min | 25% | 50% | 75% | max | |
key1 | ||||||||||||||||
a | 3.0 | 0.761376 | 0.838004 | -0.204452 | 0.494255 | 1.192962 | 1.244290 | 1.295617 | 3.0 | -0.509753 | 1.225297 | -1.675544 | -1.148349 | -0.621154 | 0.073143 | 0.767441 |
b | 2.0 | 1.001831 | 2.128543 | -0.503276 | 0.249277 | 1.001831 | 1.754384 | 2.506938 | 2.0 | -0.826561 | 0.180634 | -0.954289 | -0.890425 | -0.826561 | -0.762697 | -0.698834 |
自定义聚合函数要比图中那些经过优化的函数慢得多。这是因为在构造中间分组数据块时存在非常大的开销(函数调用、数据重排等)。
面向列的多函数应用
你可能希望对不同的列使用不同的聚合函数,或一次应用多个函数。其实这也好办,我将通过一些示例来进行讲解。
|
|
total_bill | tip | smoker | day | time | size | tip_pct | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | No | Sun | Dinner | 2 | 0.059447 |
1 | 10.34 | 1.66 | No | Sun | Dinner | 3 | 0.160542 |
2 | 21.01 | 3.50 | No | Sun | Dinner | 3 | 0.166587 |
3 | 23.68 | 3.31 | No | Sun | Dinner | 2 | 0.139780 |
4 | 24.59 | 3.61 | No | Sun | Dinner | 4 | 0.146808 |
首先,我根据天和smoker对tips进行分组:
|
|
获取分组后的一列
|
|
按照指定的方式对这一列分组进行结算
|
|
day smoker
Fri No 0.151650
Yes 0.174783
Sat No 0.158048
Yes 0.147906
Sun No 0.160113
Yes 0.187250
Thur No 0.160298
Yes 0.163863
Name: tip_pct, dtype: float64
如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数命名:
|
|
mean | std | peak_to_peak | ||
---|---|---|---|---|
day | smoker | |||
Fri | No | 0.151650 | 0.028123 | 0.067349 |
Yes | 0.174783 | 0.051293 | 0.159925 | |
Sat | No | 0.158048 | 0.039767 | 0.235193 |
Yes | 0.147906 | 0.061375 | 0.290095 | |
Sun | No | 0.160113 | 0.042347 | 0.193226 |
Yes | 0.187250 | 0.154134 | 0.644685 | |
Thur | No | 0.160298 | 0.038774 | 0.193350 |
Yes | 0.163863 | 0.039389 | 0.151240 |
你并非一定要接受GroupBy自动给出的那些列名,特别是lambda函数,它们的名称是’’,这样的辨识度就很低了(通过函数的name属性看看就知道了)。因此,如果传入的是一个由(name,function)元组组成的列表,则各元组的第一个元素就会被用作DataFrame的列名(可以将这种二元元组列表看做一个有序映射):
设置方法的字段别名
|
|
foo | bar | ||
---|---|---|---|
day | smoker | ||
Fri | No | 0.151650 | 0.028123 |
Yes | 0.174783 | 0.051293 | |
Sat | No | 0.158048 | 0.039767 |
Yes | 0.147906 | 0.061375 | |
Sun | No | 0.160113 | 0.042347 |
Yes | 0.187250 | 0.154134 | |
Thur | No | 0.160298 | 0.038774 |
Yes | 0.163863 | 0.039389 |
对于DataFrame,你还有更多选择,你可以定义一组应用于全部列的一组函数,或不同的列应用不同的函数。假设我们想要对tip_pct和total_bill列计算三个统计信息:
|
|
tip_pct | total_bill | ||||||
---|---|---|---|---|---|---|---|
count | mean | max | count | mean | max | ||
day | smoker | ||||||
Fri | No | 4 | 0.151650 | 0.187735 | 4 | 18.420000 | 22.75 |
Yes | 15 | 0.174783 | 0.263480 | 15 | 16.813333 | 40.17 | |
Sat | No | 45 | 0.158048 | 0.291990 | 45 | 19.661778 | 48.33 |
Yes | 42 | 0.147906 | 0.325733 | 42 | 21.276667 | 50.81 | |
Sun | No | 57 | 0.160113 | 0.252672 | 57 | 20.506667 | 48.17 |
Yes | 19 | 0.187250 | 0.710345 | 19 | 24.120000 | 45.35 | |
Thur | No | 45 | 0.160298 | 0.266312 | 45 | 17.113111 | 41.19 |
Yes | 17 | 0.163863 | 0.241255 | 17 | 19.190588 | 43.11 |
|
|
count | mean | max | ||
---|---|---|---|---|
day | smoker | |||
Fri | No | 4 | 0.151650 | 0.187735 |
Yes | 15 | 0.174783 | 0.263480 | |
Sat | No | 45 | 0.158048 | 0.291990 |
Yes | 42 | 0.147906 | 0.325733 | |
Sun | No | 57 | 0.160113 | 0.252672 |
Yes | 19 | 0.187250 | 0.710345 | |
Thur | No | 45 | 0.160298 | 0.266312 |
Yes | 17 | 0.163863 | 0.241255 |
跟前面一样,这里也可以传入带有自定义名称的一组元组:
|
|
tip_pct | total_bill | ||||
---|---|---|---|---|---|
Durchschnitt | Abweichung | Durchschnitt | Abweichung | ||
day | smoker | ||||
Fri | No | 0.151650 | 0.000791 | 18.420000 | 25.596333 |
Yes | 0.174783 | 0.002631 | 16.813333 | 82.562438 | |
Sat | No | 0.158048 | 0.001581 | 19.661778 | 79.908965 |
Yes | 0.147906 | 0.003767 | 21.276667 | 101.387535 | |
Sun | No | 0.160113 | 0.001793 | 20.506667 | 66.099980 |
Yes | 0.187250 | 0.023757 | 24.120000 | 109.046044 | |
Thur | No | 0.160298 | 0.001503 | 17.113111 | 59.625081 |
Yes | 0.163863 | 0.001551 | 19.190588 | 69.808518 |
现在,假设你想要对一个列或不同的列应用不同的函数。具体的办法是向agg传入一个从列名映射到函数的字典:
|
|
tip | size | ||
---|---|---|---|
day | smoker | ||
Fri | No | 3.50 | 9 |
Yes | 4.73 | 31 | |
Sat | No | 9.00 | 115 |
Yes | 10.00 | 104 | |
Sun | No | 6.00 | 167 |
Yes | 6.50 | 49 | |
Thur | No | 6.70 | 112 |
Yes | 5.00 | 40 |
|
|
tip_pct | size | |||||
---|---|---|---|---|---|---|
min | max | mean | std | sum | ||
day | smoker | |||||
Fri | No | 0.120385 | 0.187735 | 0.151650 | 0.028123 | 9 |
Yes | 0.103555 | 0.263480 | 0.174783 | 0.051293 | 31 | |
Sat | No | 0.056797 | 0.291990 | 0.158048 | 0.039767 | 115 |
Yes | 0.035638 | 0.325733 | 0.147906 | 0.061375 | 104 | |
Sun | No | 0.059447 | 0.252672 | 0.160113 | 0.042347 | 167 |
Yes | 0.065660 | 0.710345 | 0.187250 | 0.154134 | 49 | |
Thur | No | 0.072961 | 0.266312 | 0.160298 | 0.038774 | 112 |
Yes | 0.090014 | 0.241255 | 0.163863 | 0.039389 | 40 |
只有将多个函数应用到至少一列时,DataFrame才会拥有层次化的列。
以“没有行索引”的形式返回聚合数据
默认返回的数据是带分组索引的
|
|
total_bill | tip | size | tip_pct | ||
---|---|---|---|---|---|
day | smoker | ||||
Fri | No | 18.420000 | 2.812500 | 2.250000 | 0.151650 |
Yes | 16.813333 | 2.714000 | 2.066667 | 0.174783 | |
Sat | No | 19.661778 | 3.102889 | 2.555556 | 0.158048 |
Yes | 21.276667 | 2.875476 | 2.476190 | 0.147906 | |
Sun | No | 20.506667 | 3.167895 | 2.929825 | 0.160113 |
Yes | 24.120000 | 3.516842 | 2.578947 | 0.187250 | |
Thur | No | 17.113111 | 2.673778 | 2.488889 | 0.160298 |
Yes | 19.190588 | 3.030000 | 2.352941 | 0.163863 |
可以向 groupby 传入 as_index=False
以禁用该功能
|
|
day | smoker | total_bill | tip | size | tip_pct | |
---|---|---|---|---|---|---|
0 | Fri | No | 18.420000 | 2.812500 | 2.250000 | 0.151650 |
1 | Fri | Yes | 16.813333 | 2.714000 | 2.066667 | 0.174783 |
2 | Sat | No | 19.661778 | 3.102889 | 2.555556 | 0.158048 |
3 | Sat | Yes | 21.276667 | 2.875476 | 2.476190 | 0.147906 |
4 | Sun | No | 20.506667 | 3.167895 | 2.929825 | 0.160113 |
5 | Sun | Yes | 24.120000 | 3.516842 | 2.578947 | 0.187250 |
6 | Thur | No | 17.113111 | 2.673778 | 2.488889 | 0.160298 |
7 | Thur | Yes | 19.190588 | 3.030000 | 2.352941 | 0.163863 |
apply:一般性的“拆分-应用-合并”
apply会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起。
|
|
total_bill | tip | smoker | day | time | size | tip_pct | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | No | Sun | Dinner | 2 | 0.059447 |
1 | 10.34 | 1.66 | No | Sun | Dinner | 3 | 0.160542 |
2 | 21.01 | 3.50 | No | Sun | Dinner | 3 | 0.166587 |
3 | 23.68 | 3.31 | No | Sun | Dinner | 2 | 0.139780 |
4 | 24.59 | 3.61 | No | Sun | Dinner | 4 | 0.146808 |
假设你想要根据分组选出最高的5个tip_pct值。首先,编写一个选取指定列具有最大值的行的函数:
|
|
total_bill | tip | smoker | day | time | size | tip_pct | |
---|---|---|---|---|---|---|---|
109 | 14.31 | 4.00 | Yes | Sat | Dinner | 2 | 0.279525 |
183 | 23.17 | 6.50 | Yes | Sun | Dinner | 4 | 0.280535 |
232 | 11.61 | 3.39 | No | Sat | Dinner | 2 | 0.291990 |
67 | 3.07 | 1.00 | Yes | Sat | Dinner | 1 | 0.325733 |
178 | 9.60 | 4.00 | Yes | Sun | Dinner | 2 | 0.416667 |
172 | 7.25 | 5.15 | Yes | Sun | Dinner | 2 | 0.710345 |
如果对smoker分组并用该函数调用apply,就会得到:
|
|
total_bill | tip | smoker | day | time | size | tip_pct | ||
---|---|---|---|---|---|---|---|---|
smoker | ||||||||
No | 88 | 24.71 | 5.85 | No | Thur | Lunch | 2 | 0.236746 |
185 | 20.69 | 5.00 | No | Sun | Dinner | 5 | 0.241663 | |
51 | 10.29 | 2.60 | No | Sun | Dinner | 2 | 0.252672 | |
149 | 7.51 | 2.00 | No | Thur | Lunch | 2 | 0.266312 | |
232 | 11.61 | 3.39 | No | Sat | Dinner | 2 | 0.291990 | |
Yes | 109 | 14.31 | 4.00 | Yes | Sat | Dinner | 2 | 0.279525 |
183 | 23.17 | 6.50 | Yes | Sun | Dinner | 4 | 0.280535 | |
67 | 3.07 | 1.00 | Yes | Sat | Dinner | 1 | 0.325733 | |
178 | 9.60 | 4.00 | Yes | Sun | Dinner | 2 | 0.416667 | |
172 | 7.25 | 5.15 | Yes | Sun | Dinner | 2 | 0.710345 |
top函数在DataFrame的各个片段上调用,然后结果由pandas.concat组装到一起,并以分组名称进行了标记。于是,最终结果就有了一个层次化索引,其内层索引值来自原DataFrame。
如果传给apply的函数能够接受其他参数或关键字,则可以将这些内容放在函数名后面一并传入:
|
|
total_bill | tip | smoker | day | time | size | tip_pct | |||
---|---|---|---|---|---|---|---|---|---|
smoker | day | ||||||||
No | Fri | 94 | 22.75 | 3.25 | No | Fri | Dinner | 2 | 0.142857 |
Sat | 212 | 48.33 | 9.00 | No | Sat | Dinner | 4 | 0.186220 | |
Sun | 156 | 48.17 | 5.00 | No | Sun | Dinner | 6 | 0.103799 | |
Thur | 142 | 41.19 | 5.00 | No | Thur | Lunch | 5 | 0.121389 | |
Yes | Fri | 95 | 40.17 | 4.73 | Yes | Fri | Dinner | 4 | 0.117750 |
Sat | 170 | 50.81 | 10.00 | Yes | Sat | Dinner | 3 | 0.196812 | |
Sun | 182 | 45.35 | 3.50 | Yes | Sun | Dinner | 3 | 0.077178 | |
Thur | 197 | 43.11 | 5.00 | Yes | Thur | Lunch | 4 | 0.115982 |
在GroupBy中,当你调用诸如describe之类的方法时,实际上只是应用了下面两条代码的快捷方式而已:
|
|
count | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|
smoker | ||||||||
No | 151.0 | 0.159328 | 0.039910 | 0.056797 | 0.136906 | 0.155625 | 0.185014 | 0.291990 |
Yes | 93.0 | 0.163196 | 0.085119 | 0.035638 | 0.106771 | 0.153846 | 0.195059 | 0.710345 |
|
|
smoker
count No 151.000000
Yes 93.000000
mean No 0.159328
Yes 0.163196
std No 0.039910
Yes 0.085119
min No 0.056797
Yes 0.035638
25% No 0.136906
Yes 0.106771
50% No 0.155625
Yes 0.153846
75% No 0.185014
Yes 0.195059
max No 0.291990
Yes 0.710345
dtype: float64
|
|
smoker
No count 151.000000
Yes count 93.000000
No mean 0.159328
Yes mean 0.163196
No std 0.039910
Yes std 0.085119
No min 0.056797
Yes min 0.035638
No 25% 0.136906
Yes 25% 0.106771
No 50% 0.155625
Yes 50% 0.153846
No 75% 0.185014
Yes 75% 0.195059
No max 0.291990
Yes max 0.710345
dtype: float64
applay 的实现方式
|
|
smoker
No count 151.000000
mean 0.159328
std 0.039910
min 0.056797
25% 0.136906
50% 0.155625
75% 0.185014
max 0.291990
Yes count 93.000000
mean 0.163196
std 0.085119
min 0.035638
25% 0.106771
50% 0.153846
75% 0.195059
max 0.710345
Name: tip_pct, dtype: float64
禁止分组键
分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将 group_keys=False
传入 groupby 即可禁止该效果:
|
|
total_bill | tip | smoker | day | time | size | tip_pct | ||
---|---|---|---|---|---|---|---|---|
smoker | ||||||||
No | 88 | 24.71 | 5.85 | No | Thur | Lunch | 2 | 0.236746 |
185 | 20.69 | 5.00 | No | Sun | Dinner | 5 | 0.241663 | |
51 | 10.29 | 2.60 | No | Sun | Dinner | 2 | 0.252672 | |
149 | 7.51 | 2.00 | No | Thur | Lunch | 2 | 0.266312 | |
232 | 11.61 | 3.39 | No | Sat | Dinner | 2 | 0.291990 | |
Yes | 109 | 14.31 | 4.00 | Yes | Sat | Dinner | 2 | 0.279525 |
183 | 23.17 | 6.50 | Yes | Sun | Dinner | 4 | 0.280535 | |
67 | 3.07 | 1.00 | Yes | Sat | Dinner | 1 | 0.325733 | |
178 | 9.60 | 4.00 | Yes | Sun | Dinner | 2 | 0.416667 | |
172 | 7.25 | 5.15 | Yes | Sun | Dinner | 2 | 0.710345 |
|
|
total_bill | tip | smoker | day | time | size | tip_pct | |
---|---|---|---|---|---|---|---|
88 | 24.71 | 5.85 | No | Thur | Lunch | 2 | 0.236746 |
185 | 20.69 | 5.00 | No | Sun | Dinner | 5 | 0.241663 |
51 | 10.29 | 2.60 | No | Sun | Dinner | 2 | 0.252672 |
149 | 7.51 | 2.00 | No | Thur | Lunch | 2 | 0.266312 |
232 | 11.61 | 3.39 | No | Sat | Dinner | 2 | 0.291990 |
109 | 14.31 | 4.00 | Yes | Sat | Dinner | 2 | 0.279525 |
183 | 23.17 | 6.50 | Yes | Sun | Dinner | 4 | 0.280535 |
67 | 3.07 | 1.00 | Yes | Sat | Dinner | 1 | 0.325733 |
178 | 9.60 | 4.00 | Yes | Sun | Dinner | 2 | 0.416667 |
172 | 7.25 | 5.15 | Yes | Sun | Dinner | 2 | 0.710345 |
分位数和桶分析
pandas有一些能根据指定面元或样本分位数将数据拆分成多块的工具(比如cut和qcut)。将这些函数跟groupby结合起来,就能非常轻松地实现对数据集的桶(bucket)或分位数(quantile)分析了。以下面这个简单的随机数据集为例,我们利用cut将其装入长度相等的桶中:
|
|
0 (-1.698, 0.0798]
1 (0.0798, 1.857]
2 (-1.698, 0.0798]
3 (0.0798, 1.857]
4 (-1.698, 0.0798]
Name: data1, dtype: category
Categories (4, interval[float64]): [(-3.482, -1.698] < (-1.698, 0.0798] < (0.0798, 1.857] < (1.857, 3.635]]
由cut返回的Categorical对象可直接传递到groupby。因此,我们可以像下面这样对data2列做一些统计计算:
|
|
count | max | mean | min | |
---|---|---|---|---|
data1 | ||||
(-3.482, -1.698] | 38.0 | 2.161012 | 0.286213 | -1.820655 |
(-1.698, 0.0798] | 472.0 | 3.762126 | -0.044402 | -3.260810 |
(0.0798, 1.857] | 452.0 | 3.026034 | -0.040110 | -3.122991 |
(1.857, 3.635] | 38.0 | 1.482856 | -0.191989 | -1.969943 |
这些都是长度相等的桶。要根据样本分位数得到大小相等的桶,使用qcut即可。传入 labels=False
即可只获取分位数的编号:
|
|
0 1
1 5
2 3
3 5
4 3
Name: data1, dtype: int64
|
|
0 (-1.261, -0.827]
1 (0.0684, 0.326]
2 (-0.476, -0.193]
3 (0.0684, 0.326]
4 (-0.476, -0.193]
Name: data1, dtype: category
Categories (10, interval[float64]): [(-3.476, -1.261] < (-1.261, -0.827] < (-0.827, -0.476] < (-0.476, -0.193] ... (0.326, 0.596] < (0.596, 0.925] < (0.925, 1.368] < (1.368, 3.635]]
|
|
count | max | mean | min | |
---|---|---|---|---|
data1 | ||||
0 | 100.0 | 2.621324 | 0.070152 | -2.179777 |
1 | 100.0 | 2.462273 | -0.049077 | -2.289337 |
2 | 100.0 | 3.762126 | -0.077355 | -3.260810 |
3 | 100.0 | 2.707121 | 0.016885 | -2.551321 |
4 | 100.0 | 1.805644 | -0.043425 | -2.499053 |
5 | 100.0 | 2.079105 | -0.021532 | -2.564312 |
6 | 100.0 | 3.026034 | -0.123689 | -3.122991 |
7 | 100.0 | 2.220393 | -0.105029 | -2.630459 |
8 | 100.0 | 2.454573 | -0.046064 | -2.195948 |
9 | 100.0 | 2.476637 | 0.024066 | -2.050193 |
示例:用特定于分组的值填充缺失值
对于缺失数据的清理工作,有时你会用dropna将其替换掉,而有时则可能会希望用一个固定值或由数据集本身所衍生出来的值去填充NA值。这时就得使用fillna这个工具了。在下面这个例子中,我用平均值去填充NA值:
|
|
0 NaN
1 -1.698228
2 NaN
3 0.250362
4 NaN
5 -0.981594
dtype: float64
|
|
0 -0.809820
1 -1.698228
2 -0.809820
3 0.250362
4 -0.809820
5 -0.981594
dtype: float64
假设你需要对不同的分组填充不同的值。一种方法是将数据分组,并使用apply和一个能够对各数据块调用fillna的函数即可。
|
|
['East', 'East', 'East', 'East', 'West', 'West', 'West', 'West']
|
|
Ohio -0.343101
New York 1.024389
Vermont 1.311144
Florida -0.423204
Oregon 0.114283
Nevada -1.089131
California -1.727345
Idaho -0.830830
dtype: float64
|
|
Ohio -0.343101
New York 1.024389
Vermont NaN
Florida -0.423204
Oregon 0.114283
Nevada NaN
California -1.727345
Idaho NaN
dtype: float64
|
|
East 0.086028
West -0.806531
dtype: float64
我们可以用分组平均值去填充NA值:
|
|
Ohio -0.343101
New York 1.024389
Vermont 0.086028
Florida -0.423204
Oregon 0.114283
Nevada -0.806531
California -1.727345
Idaho -0.806531
dtype: float64
也可以在代码中预定义各组的填充值。由于分组具有一个name属性,所以我们可以拿来用一下:
|
|
Ohio -0.343101
New York 1.024389
Vermont 0.500000
Florida -0.423204
Oregon 0.114283
Nevada -1.000000
California -1.727345
Idaho -1.000000
dtype: float64
示例:分组加权平均数和相关系数
根据groupby的“拆分-应用-合并”范式,可以进行DataFrame的列与列之间或两个Series之间的运算(比如分组加权平均)。以下面这个数据集为例,它含有分组键、值以及一些权重值:
|
|
category | data | weights | |
---|---|---|---|
0 | a | -0.980463 | 0.247153 |
1 | a | 0.148452 | 0.912352 |
2 | a | -1.067199 | 0.719493 |
3 | a | 1.837065 | 0.883523 |
4 | b | -1.030439 | 0.907246 |
5 | b | 0.120672 | 0.538387 |
6 | b | -0.306529 | 0.423223 |
7 | b | 0.709020 | 0.552725 |
利用 category 计算分组加权平均数:
|
|
category
a 0.270898
b -0.250964
dtype: float64
另一个例子,考虑一个来自Yahoo!Finance的数据集,其中含有几只股票和标准普尔500指数(符号SPX)的收盘价:
|
|
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 2214 entries, 2003-01-02 to 2011-10-14
Data columns (total 4 columns):
AAPL 2214 non-null float64
MSFT 2214 non-null float64
XOM 2214 non-null float64
SPX 2214 non-null float64
dtypes: float64(4)
memory usage: 86.5 KB
|
|
AAPL | MSFT | XOM | SPX | |
---|---|---|---|---|
2003-01-02 | 7.40 | 21.11 | 29.22 | 909.03 |
2003-01-03 | 7.45 | 21.14 | 29.24 | 908.59 |
2003-01-06 | 7.45 | 21.52 | 29.96 | 929.01 |
2003-01-07 | 7.43 | 21.93 | 28.95 | 922.93 |
2003-01-08 | 7.28 | 21.31 | 28.83 | 909.93 |
来做一个比较有趣的任务:计算一个由日收益率(通过百分数变化计算)与SPX之间的年度相关系数组成的DataFrame。下面是一个实现办法,我们先创建一个函数,用它计算每列和SPX列的成对相关系数:
|
|
AAPL | MSFT | XOM | SPX | |
---|---|---|---|---|
2003-01-03 | 0.006757 | 0.001421 | 0.000684 | -0.000484 |
2003-01-06 | 0.000000 | 0.017975 | 0.024624 | 0.022474 |
2003-01-07 | -0.002685 | 0.019052 | -0.033712 | -0.006545 |
2003-01-08 | -0.020188 | -0.028272 | -0.004145 | -0.014086 |
2003-01-09 | 0.008242 | 0.029094 | 0.021159 | 0.019386 |
最后,我们用年对百分比变化进行分组,可以用一个一行的函数,从每行的标签返回每个datetime标签的year属性:
|
|
AAPL | MSFT | XOM | SPX | |
---|---|---|---|---|
2003 | 0.541124 | 0.745174 | 0.661265 | 1.0 |
2004 | 0.374283 | 0.588531 | 0.557742 | 1.0 |
2005 | 0.467540 | 0.562374 | 0.631010 | 1.0 |
2006 | 0.428267 | 0.406126 | 0.518514 | 1.0 |
2007 | 0.508118 | 0.658770 | 0.786264 | 1.0 |
2008 | 0.681434 | 0.804626 | 0.828303 | 1.0 |
2009 | 0.707103 | 0.654902 | 0.797921 | 1.0 |
2010 | 0.710105 | 0.730118 | 0.839057 | 1.0 |
2011 | 0.691931 | 0.800996 | 0.859975 | 1.0 |
还可以计算列与列之间的相关系数。这里,我们计算Apple和Microsoft的年相关系数:
|
|
2003 0.480868
2004 0.259024
2005 0.300093
2006 0.161735
2007 0.417738
2008 0.611901
2009 0.432738
2010 0.571946
2011 0.581987
dtype: float64
示例:组级别的线性回归
顺着上一个例子继续,你可以用groupby执行更为复杂的分组统计分析,只要函数返回的是pandas对象或标量值即可。例如,我可以定义下面这个regress函数(利用statsmodels计量经济学库)对各数据块执行普通最小二乘法(Ordinary Least Squares,OLS)回归:
|
|
现在,为了按年计算AAPL对SPX收益率的线性回归,执行:
|
|
SPX | intercept | |
---|---|---|
2003 | 1.195406 | 0.000710 |
2004 | 1.363463 | 0.004201 |
2005 | 1.766415 | 0.003246 |
2006 | 1.645496 | 0.000080 |
2007 | 1.198761 | 0.003438 |
2008 | 0.968016 | -0.001110 |
2009 | 0.879103 | 0.002954 |
2010 | 1.052608 | 0.001261 |
2011 | 0.806605 | 0.001514 |
透视表和交叉表
透视表(pivot table)是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行聚合,并根据行和列上的分组键将数据分配到各个矩形区域中。在Python和pandas中,可以通过本章所介绍的groupby功能以及(能够利用层次化索引的)重塑运算制作透视表。DataFrame有一个pivot_table方法,此外还有一个顶级的pandas.pivot_table函数。除能为groupby提供便利之外,pivot_table还可以添加分项小计,也叫做margins。
|
|
total_bill | tip | smoker | day | time | size | tip_pct | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | No | Sun | Dinner | 2 | 0.059447 |
1 | 10.34 | 1.66 | No | Sun | Dinner | 3 | 0.160542 |
2 | 21.01 | 3.50 | No | Sun | Dinner | 3 | 0.166587 |
3 | 23.68 | 3.31 | No | Sun | Dinner | 2 | 0.139780 |
4 | 24.59 | 3.61 | No | Sun | Dinner | 4 | 0.146808 |
假设我想要根据day和smoker计算分组平均数(pivot_table的默认聚合类型),并将day和smoker放到行上:
|
|
size | tip | tip_pct | total_bill | ||
---|---|---|---|---|---|
day | smoker | ||||
Fri | No | 2.250000 | 2.812500 | 0.151650 | 18.420000 |
Yes | 2.066667 | 2.714000 | 0.174783 | 16.813333 | |
Sat | No | 2.555556 | 3.102889 | 0.158048 | 19.661778 |
Yes | 2.476190 | 2.875476 | 0.147906 | 21.276667 | |
Sun | No | 2.929825 | 3.167895 | 0.160113 | 20.506667 |
Yes | 2.578947 | 3.516842 | 0.187250 | 24.120000 | |
Thur | No | 2.488889 | 2.673778 | 0.160298 | 17.113111 |
Yes | 2.352941 | 3.030000 | 0.163863 | 19.190588 |
可以用groupby直接来做。现在,假设我们只想聚合tip_pct和size,而且想根据time进行分组。我将smoker放到列上,把day放到行上:
|
|
size | tip_pct | ||||
---|---|---|---|---|---|
smoker | No | Yes | No | Yes | |
time | day | ||||
Dinner | Fri | 2.000000 | 2.222222 | 0.139622 | 0.165347 |
Sat | 2.555556 | 2.476190 | 0.158048 | 0.147906 | |
Sun | 2.929825 | 2.578947 | 0.160113 | 0.187250 | |
Thur | 2.000000 | NaN | 0.159744 | NaN | |
Lunch | Fri | 3.000000 | 1.833333 | 0.187735 | 0.188937 |
Thur | 2.500000 | 2.352941 | 0.160311 | 0.163863 |
|
|
size | tip_pct | ||
---|---|---|---|
time | day | ||
Dinner | Fri | 2.166667 | 0.158916 |
Sat | 2.517241 | 0.153152 | |
Sun | 2.842105 | 0.166897 | |
Thur | 2.000000 | 0.159744 | |
Lunch | Fri | 2.000000 | 0.188765 |
Thur | 2.459016 | 0.161301 |
还可以对这个表作进一步的处理,传入 margins=True
添加分项小计。这将会添加标签为All的行和列,其值对应于单个等级中所有数据的分组统计:
|
|
size | tip_pct | ||||||
---|---|---|---|---|---|---|---|
smoker | No | Yes | All | No | Yes | All | |
time | day | ||||||
Dinner | Fri | 2.000000 | 2.222222 | 2.166667 | 0.139622 | 0.165347 | 0.158916 |
Sat | 2.555556 | 2.476190 | 2.517241 | 0.158048 | 0.147906 | 0.153152 | |
Sun | 2.929825 | 2.578947 | 2.842105 | 0.160113 | 0.187250 | 0.166897 | |
Thur | 2.000000 | NaN | 2.000000 | 0.159744 | NaN | 0.159744 | |
Lunch | Fri | 3.000000 | 1.833333 | 2.000000 | 0.187735 | 0.188937 | 0.188765 |
Thur | 2.500000 | 2.352941 | 2.459016 | 0.160311 | 0.163863 | 0.161301 | |
All | 2.668874 | 2.408602 | 2.569672 | 0.159328 | 0.163196 | 0.160803 |
要使用其他的聚合函数,将其传给aggfunc即可。例如,使用count或len可以得到有关分组大小的交叉表(计数或频率):
|
|
day | Fri | Sat | Sun | Thur | All | |
---|---|---|---|---|---|---|
time | smoker | |||||
Dinner | No | 3.0 | 45.0 | 57.0 | 1.0 | 106.0 |
Yes | 9.0 | 42.0 | 19.0 | NaN | 70.0 | |
Lunch | No | 1.0 | NaN | NaN | 44.0 | 45.0 |
Yes | 6.0 | NaN | NaN | 17.0 | 23.0 | |
All | 19.0 | 87.0 | 76.0 | 62.0 | 244.0 |
如果存在空的组合(也就是NA),你可能会希望设置一个fill_value:
|
|
day | Fri | Sat | Sun | Thur | ||
---|---|---|---|---|---|---|
time | size | smoker | ||||
Dinner | 1 | No | 0.000000 | 0.137931 | 0.000000 | 0.000000 |
Yes | 0.000000 | 0.325733 | 0.000000 | 0.000000 | ||
2 | No | 0.139622 | 0.162705 | 0.168859 | 0.159744 | |
Yes | 0.171297 | 0.148668 | 0.207893 | 0.000000 | ||
3 | No | 0.000000 | 0.154661 | 0.152663 | 0.000000 | |
Yes | 0.000000 | 0.144995 | 0.152660 | 0.000000 | ||
4 | No | 0.000000 | 0.150096 | 0.148143 | 0.000000 | |
Yes | 0.117750 | 0.124515 | 0.193370 | 0.000000 | ||
5 | No | 0.000000 | 0.000000 | 0.206928 | 0.000000 | |
Yes | 0.000000 | 0.106572 | 0.065660 | 0.000000 | ||
6 | No | 0.000000 | 0.000000 | 0.103799 | 0.000000 | |
Lunch | 1 | No | 0.000000 | 0.000000 | 0.000000 | 0.181728 |
Yes | 0.223776 | 0.000000 | 0.000000 | 0.000000 | ||
2 | No | 0.000000 | 0.000000 | 0.000000 | 0.166005 | |
Yes | 0.181969 | 0.000000 | 0.000000 | 0.158843 | ||
3 | No | 0.187735 | 0.000000 | 0.000000 | 0.084246 | |
Yes | 0.000000 | 0.000000 | 0.000000 | 0.204952 | ||
4 | No | 0.000000 | 0.000000 | 0.000000 | 0.138919 | |
Yes | 0.000000 | 0.000000 | 0.000000 | 0.155410 | ||
5 | No | 0.000000 | 0.000000 | 0.000000 | 0.121389 | |
6 | No | 0.000000 | 0.000000 | 0.000000 | 0.173706 |
pivot_table的参数说明请参见表
pandas高级应用
分类数据
通过使用它,提高性能和内存的使用率。
背景和目的
表中的一列通常会有重复的包含不同值的小集合的情况。我们已经学过了unique和value_counts,它们可以从数组提取出不同的值,并分别计算频率:
|
|
0 apple
1 orange
2 apple
3 apple
4 apple
5 orange
6 apple
7 apple
dtype: object
|
|
array(['apple', 'orange'], dtype=object)
|
|
apple 6
orange 2
dtype: int64
许多数据系统(数据仓库、统计计算或其它应用)都发展出了特定的表征重复值的方法,以进行高效的存储和计算。在数据仓库中,最好的方法是使用所谓的包含不同值的维表(Dimension Table),将主要的参数存储为引用维表整数键:
就像 mysql 的分表存储
|
|
0 0
1 1
2 0
3 0
4 0
5 1
6 0
7 0
dtype: int64
|
|
0 apple
1 orange
dtype: object
可以使用take方法存储原始的字符串Series:
|
|
0 apple
1 orange
0 apple
0 apple
0 apple
1 orange
0 apple
0 apple
dtype: object
这种用整数表示的方法称为分类或字典编码表示法。不同值得数组称为分类、字典或数据级。本书中,我们使用分类的说法。表示分类的整数值称为分类编码或简单地称为编码。
pandas的分类类型
pandas有一个特殊的分类类型,用于保存使用整数分类表示法的数据。
|
|
basket_id | fruit | count | weight | |
---|---|---|---|---|
0 | 0 | apple | 11 | 0.661412 |
1 | 1 | orange | 6 | 2.661072 |
2 | 2 | apple | 10 | 3.956839 |
3 | 3 | apple | 10 | 3.491835 |
4 | 4 | apple | 6 | 2.954149 |
5 | 5 | orange | 12 | 3.967850 |
6 | 6 | apple | 8 | 0.093289 |
7 | 7 | apple | 3 | 3.056569 |
这里,df['fruit']
是一个Python字符串对象的数组。我们可以通过调用它,将它转变为分类:
|
|
0 apple
1 orange
2 apple
3 apple
4 apple
5 orange
6 apple
7 apple
Name: fruit, dtype: category
Categories (2, object): [apple, orange]
fruit_cat的值不是NumPy数组,而是一个pandas.Categorical实例:
|
|
[apple, orange, apple, apple, apple, orange, apple, apple]
Categories (2, object): [apple, orange]
|
|
pandas.core.arrays.categorical.Categorical
分类对象有categories和codes属性:
|
|
Index(['apple', 'orange'], dtype='object')
|
|
array([0, 1, 0, 0, 0, 1, 0, 0], dtype=int8)
你可将DataFrame的列通过分配转换结果,转换为分类:
|
|
|
|
basket_id | fruit | count | weight | |
---|---|---|---|---|
0 | 0 | apple | 11 | 0.661412 |
1 | 1 | orange | 6 | 2.661072 |
2 | 2 | apple | 10 | 3.956839 |
3 | 3 | apple | 10 | 3.491835 |
4 | 4 | apple | 6 | 2.954149 |
5 | 5 | orange | 12 | 3.967850 |
6 | 6 | apple | 8 | 0.093289 |
7 | 7 | apple | 3 | 3.056569 |
|
|
0 apple
1 orange
2 apple
3 apple
4 apple
5 orange
6 apple
7 apple
Name: fruit, dtype: category
Categories (2, object): [apple, orange]
你还可以从其它Python序列直接创建pandas.Categorical:
|
|
[foo, bar, baz, foo, bar]
Categories (3, object): [bar, baz, foo]
如果你已经从其它源获得了分类编码,你还可以使用from_codes构造器:
|
|
[foo, bar, baz, foo, foo, bar]
Categories (3, object): [foo, bar, baz]
与显示指定不同,分类变换不认定指定的分类顺序。因此取决于输入数据的顺序,categories数组的顺序会不同。当使用from_codes或其它的构造器时,你可以指定分类一个有意义的顺序:
|
|
[foo, bar, baz, foo, foo, bar]
Categories (3, object): [foo < bar < baz]
无序的分类实例可以通过as_ordered排序:
|
|
[foo, bar, baz, foo, foo, bar]
Categories (3, object): [foo < bar < baz]
用分类进行计算
来看一些随机的数值数据,使用pandas.qcut面元函数。它会返回pandas.Categorical,我们之前使用过pandas.cut,但没解释分类是如何工作的:
|
|
array([-0.20470766, 0.47894334, -0.51943872, -0.5557303 , 1.96578057])
计算这个数据的分位面元,提取一些统计信息:
|
|
[(-0.684, -0.0101], (-0.0101, 0.63], (-0.684, -0.0101], (-0.684, -0.0101], (0.63, 3.928], ..., (-0.0101, 0.63], (-0.684, -0.0101], (-2.9499999999999997, -0.684], (-0.0101, 0.63], (0.63, 3.928]]
Length: 1000
Categories (4, interval[float64]): [(-2.9499999999999997, -0.684] < (-0.684, -0.0101] < (-0.0101, 0.63] < (0.63, 3.928]]
虽然有用,确切的样本分位数与分位的名称相比,不利于生成汇总。我们可以使用labels参数qcut,实现目的:
|
|
[Q2, Q3, Q2, Q2, Q4, ..., Q3, Q2, Q1, Q3, Q4]
Length: 1000
Categories (4, object): [Q1 < Q2 < Q3 < Q4]
|
|
array([1, 2, 1, 1, 3, 3, 2, 2, 3, 3], dtype=int8)
加上标签的面元分类不包含数据面元边界的信息,因此可以使用groupby提取一些汇总信息:
|
|
0 Q2
1 Q3
2 Q2
3 Q2
4 Q4
Name: quartile, dtype: category
Categories (4, object): [Q1 < Q2 < Q3 < Q4]
|
|
quartile | count | min | max | |
---|---|---|---|---|
0 | Q1 | 250 | -2.949343 | -0.685484 |
1 | Q2 | 250 | -0.683066 | -0.010115 |
2 | Q3 | 250 | -0.010032 | 0.628894 |
3 | Q4 | 250 | 0.634238 | 3.927528 |
分位数列保存了原始的面元分类信息,包括排序:
|
|
0 Q1
1 Q2
2 Q3
3 Q4
Name: quartile, dtype: category
Categories (4, object): [Q1 < Q2 < Q3 < Q4]
用分类提高性能
如果你是在一个特定数据集上做大量分析,将其转换为分类可以极大地提高效率。DataFrame列的分类使用的内存通常少的多。来看一些包含一千万元素的Series,和一些不同的分类:
|
|
现在,将标签转换为分类:
|
|
这时,可以看到标签使用的内存远比分类多:
|
|
80000080
|
|
10000272
转换为分类不是没有代价的,但这是一次性的代价:
|
|
Wall time: 429 ms
GroupBy使用分类操作明显更快,是因为底层的算法使用整数编码数组,而不是字符串数组。
分类方法
包含分类数据的Series有一些特殊的方法,类似于Series.str字符串方法。它还提供了方便的分类和编码的使用方法。看下面的Series:
|
|
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
dtype: category
Categories (4, object): [a, b, c, d]
特别的cat属性提供了分类方法的入口:
|
|
0 0
1 1
2 2
3 3
4 0
5 1
6 2
7 3
dtype: int8
|
|
Index(['a', 'b', 'c', 'd'], dtype='object')
假设我们知道这个数据的实际分类集,超出了数据中的四个值。我们可以使用set_categories方法改变它们:
|
|
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
dtype: category
Categories (5, object): [a, b, c, d, e]
虽然数据看起来没变,新的分类将反映在它们的操作中。例如,如果有的话,value_counts表示分类:
|
|
d 2
c 2
b 2
a 2
dtype: int64
|
|
d 2
c 2
b 2
a 2
e 0
dtype: int64
在大数据集中,分类经常作为节省内存和高性能的便捷工具。过滤完大DataFrame或Series之后,许多分类可能不会出现在数据中。我们可以使用remove_unused_categories方法删除没看到的分类:
|
|
0 a
1 b
4 a
5 b
dtype: category
Categories (4, object): [a, b, c, d]
|
|
0 a
1 b
4 a
5 b
dtype: category
Categories (2, object): [a, b]
可用的分类方法
为建模创建虚拟变量
当你使用统计或机器学习工具时,通常会将分类数据转换为虚拟变量,也称为one-hot编码。这包括创建一个不同类别的列的DataFrame;这些列包含给定分类的1s,其它为0。
|
|
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
dtype: category
Categories (4, object): [a, b, c, d]
pandas.get_dummies 函数可以转换这个分类数据为包含虚拟变量的DataFrame:
|
|
a | b | c | d | |
---|---|---|---|---|
0 | 1 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 |
2 | 0 | 0 | 1 | 0 |
3 | 0 | 0 | 0 | 1 |
4 | 1 | 0 | 0 | 0 |
5 | 0 | 1 | 0 | 0 |
6 | 0 | 0 | 1 | 0 |
7 | 0 | 0 | 0 | 1 |
GroupBy高级应用
分组转换
在分组操作中学习了apply方法,进行转换。还有另一个transform方法,它与apply很像,但是对使用的函数有一定限制:
- 它可以产生向分组形状广播标量值
- 它可以产生一个和输入组形状相同的对象
- 它不能修改输入
|
|
key | value | |
---|---|---|
0 | a | 0.0 |
1 | b | 1.0 |
2 | c | 2.0 |
3 | a | 3.0 |
4 | b | 4.0 |
5 | c | 5.0 |
6 | a | 6.0 |
7 | b | 7.0 |
8 | c | 8.0 |
9 | a | 9.0 |
10 | b | 10.0 |
11 | c | 11.0 |
按键进行分组:
|
|
key
a 4.5
b 5.5
c 6.5
Name: value, dtype: float64
假设我们想产生一个和 df['value']
形状相同的Series,但值替换为按键分组的平均值。我们可以传递函数 lambda x: x.mean()
进行转换:
|
|
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
对于内置的聚合函数,我们可以传递一个字符串假名作为GroupBy的agg方法:
|
|
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
与apply类似,transform的函数会返回Series,但是结果必须与输入大小相同。举个例子,我们可以用lambda函数将每个分组乘以2:
|
|
0 0.0
1 2.0
2 4.0
3 6.0
4 8.0
5 10.0
6 12.0
7 14.0
8 16.0
9 18.0
10 20.0
11 22.0
Name: value, dtype: float64
看一个由简单聚合构造的的分组转换函数:
我们用transform或apply可以获得等价的结果:
|
|
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
|
|
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
分组的时间重采样
对于时间序列数据,resample方法从语义上是一个基于内在时间的分组操作。
|
|
time | value | |
---|---|---|
0 | 2017-05-20 00:00:00 | 0 |
1 | 2017-05-20 00:01:00 | 1 |
2 | 2017-05-20 00:02:00 | 2 |
3 | 2017-05-20 00:03:00 | 3 |
4 | 2017-05-20 00:04:00 | 4 |
5 | 2017-05-20 00:05:00 | 5 |
6 | 2017-05-20 00:06:00 | 6 |
7 | 2017-05-20 00:07:00 | 7 |
8 | 2017-05-20 00:08:00 | 8 |
9 | 2017-05-20 00:09:00 | 9 |
10 | 2017-05-20 00:10:00 | 10 |
11 | 2017-05-20 00:11:00 | 11 |
12 | 2017-05-20 00:12:00 | 12 |
13 | 2017-05-20 00:13:00 | 13 |
14 | 2017-05-20 00:14:00 | 14 |
这里,我们可以用time作为索引,然后重采样:
|
|
value | |
---|---|
time | |
2017-05-20 00:00:00 | 5 |
2017-05-20 00:05:00 | 5 |
2017-05-20 00:10:00 | 5 |
假设DataFrame包含多个时间序列,用一个额外的分组键的列进行标记:
|
|
time | key | value | |
---|---|---|---|
0 | 2017-05-20 00:00:00 | a | 0.0 |
1 | 2017-05-20 00:00:00 | b | 1.0 |
2 | 2017-05-20 00:00:00 | c | 2.0 |
3 | 2017-05-20 00:01:00 | a | 3.0 |
4 | 2017-05-20 00:01:00 | b | 4.0 |
|